From 59af7c99bcabbf982830de6aee149817c8637313 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Provazn=C3=ADk?= Date: Wed, 11 Mar 2026 17:54:10 +0100 Subject: [PATCH] Revert "Migrate Exec task to TaskEnvironment API (#13171)" This reverts commit 074bba0ef36d6386e022c27a37a8dc61372eea37. --- src/Tasks.UnitTests/Exec_Tests.cs | 163 ------------------ src/Tasks/Exec.cs | 74 +------- src/UnitTests.Shared/TaskEnvironmentHelper.cs | 19 -- 3 files changed, 7 insertions(+), 249 deletions(-) diff --git a/src/Tasks.UnitTests/Exec_Tests.cs b/src/Tasks.UnitTests/Exec_Tests.cs index 175b869f5f9..3a820e75421 100644 --- a/src/Tasks.UnitTests/Exec_Tests.cs +++ b/src/Tasks.UnitTests/Exec_Tests.cs @@ -37,7 +37,6 @@ private Exec PrepareExec(string command) { IBuildEngine2 mockEngine = new MockEngine(_output); Exec exec = new Exec(); - exec.TaskEnvironment = TaskEnvironmentHelper.CreateForTest(); exec.BuildEngine = mockEngine; exec.Command = command; return exec; @@ -47,7 +46,6 @@ private ExecWrapper PrepareExecWrapper(string command) { IBuildEngine2 mockEngine = new MockEngine(_output); ExecWrapper exec = new ExecWrapper(); - exec.TaskEnvironment = TaskEnvironmentHelper.CreateForTest(); exec.BuildEngine = mockEngine; exec.Command = command; return exec; @@ -907,7 +905,6 @@ public void ValidateParametersNoCommand() public void SetEnvironmentVariableParameter() { Exec exec = new Exec(); - exec.TaskEnvironment = TaskEnvironmentHelper.CreateForTest(); exec.BuildEngine = new MockEngine(); exec.Command = NativeMethodsShared.IsWindows ? "echo [%MYENVVAR%]" : "echo [$myenvvar]"; exec.EnvironmentVariables = new[] { "myenvvar=myvalue" }; @@ -1072,166 +1069,6 @@ public void ConsoleOutputDoesNotTrimLeadingWhitespace() exec.ConsoleOutput[0].ItemSpec.ShouldBe(lineWithLeadingWhitespace); } } - - /// - /// Runs an Exec task that lists directory contents and asserts expected/unexpected files in the output. - /// - /// The TaskEnvironment to configure on the Exec task. - /// The WorkingDirectory to set, or null to use the default. - /// A filename that must appear in the output. - /// A filename that must NOT appear in the output, or null to skip. - private void ExecuteListCommandInDirectory( - TaskEnvironment taskEnvironment, - string workingDirectory, - string expectedFile, - string notExpectedFile = null) - { - Exec exec = new Exec(); - exec.TaskEnvironment = taskEnvironment; - exec.BuildEngine = new MockEngine(_output); - exec.Command = NativeMethodsShared.IsWindows ? "dir /b" : "ls"; - exec.ConsoleToMSBuild = true; - - if (workingDirectory != null) - { - exec.WorkingDirectory = workingDirectory; - } - - bool result = exec.Execute(); - - result.ShouldBeTrue(); - ((MockEngine)exec.BuildEngine).AssertLogContains(expectedFile); - if (notExpectedFile != null) - { - ((MockEngine)exec.BuildEngine).AssertLogDoesntContain(notExpectedFile); - } - } - - /// - /// Verify that Exec resolves relative WorkingDirectory via TaskEnvironment.GetAbsolutePath in multiprocess mode. - /// - [Fact] - public void ExecResolvesRelativeWorkingDirectoryWithMultiProcessDriver() - { - using (var testEnv = TestEnvironment.Create(_output)) - { - var projectDir = testEnv.CreateFolder(); - var subDir = Directory.CreateDirectory(Path.Combine(projectDir.Path, "subdir")); - File.WriteAllText(Path.Combine(subDir.FullName, "testfile.txt"), "test content"); - - var differentDir = testEnv.CreateFolder(); - var decoySubDir = Directory.CreateDirectory(Path.Combine(differentDir.Path, "subdir")); - File.WriteAllText(Path.Combine(decoySubDir.FullName, "decoyfile.txt"), "decoy content"); - - string originalDirectory = Directory.GetCurrentDirectory(); - try - { - Directory.SetCurrentDirectory(projectDir.Path); - - ExecuteListCommandInDirectory( - TaskEnvironmentHelper.CreateForTest(), - workingDirectory: "subdir", - expectedFile: "testfile.txt", - notExpectedFile: "decoyfile.txt"); - } - finally - { - Directory.SetCurrentDirectory(originalDirectory); - } - } - } - - /// - /// Verify that Exec uses TaskEnvironment.ProjectDirectory when WorkingDirectory is not specified. - /// Uses MultiThreadedTaskEnvironmentDriver so process CWD differs from project directory. - /// - [Fact] - public void ExecUsesProjectDirectoryAsDefaultWorkingDirectory() - { - using (var testEnv = TestEnvironment.Create(_output)) - { - var projectDir = testEnv.CreateFolder(); - File.WriteAllText(Path.Combine(projectDir.Path, "projectfile.txt"), "project content"); - - var differentCwd = testEnv.CreateFolder(); - File.WriteAllText(Path.Combine(differentCwd.Path, "decoyfile.txt"), "decoy content"); - - string originalDirectory = Directory.GetCurrentDirectory(); - TaskEnvironment taskEnvironment = null; - try - { - Directory.SetCurrentDirectory(differentCwd.Path); - - taskEnvironment = TaskEnvironmentHelper.CreateMultithreadedForTest(projectDir.Path); - ExecuteListCommandInDirectory( - taskEnvironment, - workingDirectory: null, - expectedFile: "projectfile.txt", - notExpectedFile: "decoyfile.txt"); - } - finally - { - taskEnvironment?.Dispose(); - Directory.SetCurrentDirectory(originalDirectory); - } - } - } - - /// - /// Verify that Exec correctly handles absolute WorkingDirectory paths. - /// - [Fact] - public void ExecHandlesAbsoluteWorkingDirectory() - { - using (var testEnv = TestEnvironment.Create(_output)) - { - var workDir = testEnv.CreateFolder(); - File.WriteAllText(Path.Combine(workDir.Path, "absolutedir.txt"), "absolute content"); - - ExecuteListCommandInDirectory( - TaskEnvironmentHelper.CreateForTest(), - workingDirectory: workDir.Path, - expectedFile: "absolutedir.txt"); - } - } - - /// - /// Verify that Exec resolves relative WorkingDirectory relative to TaskEnvironment.ProjectDirectory, - /// not the process current directory. Uses MultiThreadedTaskEnvironmentDriver to simulate - /// multithreaded mode where process CWD differs from project directory. - /// - [Fact] - public void ExecResolvesRelativeWorkingDirectoryRelativeToProjectDirectory() - { - using (var testEnv = TestEnvironment.Create(_output)) - { - var projectDir = testEnv.CreateFolder(); - var subDir = Directory.CreateDirectory(Path.Combine(projectDir.Path, "builddir")); - File.WriteAllText(Path.Combine(subDir.FullName, "multithreaded.txt"), "multithreaded content"); - - var differentCwd = testEnv.CreateFolder(); - File.WriteAllText(Path.Combine(differentCwd.Path, "decoyfile.txt"), "decoy content"); - - string originalDirectory = Directory.GetCurrentDirectory(); - TaskEnvironment taskEnvironment = null; - try - { - Directory.SetCurrentDirectory(differentCwd.Path); - - taskEnvironment = TaskEnvironmentHelper.CreateMultithreadedForTest(projectDir.Path); - ExecuteListCommandInDirectory( - taskEnvironment, - workingDirectory: "builddir", - expectedFile: "multithreaded.txt", - notExpectedFile: "decoyfile.txt"); - } - finally - { - taskEnvironment?.Dispose(); - Directory.SetCurrentDirectory(originalDirectory); - } - } - } } internal sealed class ExecWrapper : Exec diff --git a/src/Tasks/Exec.cs b/src/Tasks/Exec.cs index 3c557005442..d59c05f4d04 100644 --- a/src/Tasks/Exec.cs +++ b/src/Tasks/Exec.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Text; @@ -22,8 +21,7 @@ namespace Microsoft.Build.Tasks /// for it to complete, and then returns True if the process completed successfully, and False if an error occurred. /// // UNDONE: ToolTask has a "UseCommandProcessor" flag that duplicates much of the code in this class. Remove the duplication. - [MSBuildMultiThreadableTask] - public class Exec : ToolTaskExtension, IMultiThreadableTask + public class Exec : ToolTaskExtension { #region Constructors @@ -48,7 +46,7 @@ public Exec() // Are the encodings for StdErr and StdOut streams valid private bool _encodingParametersValid = true; - private AbsolutePath _workingDirectory; + private string _workingDirectory; private ITaskItem[] _outputs; internal bool workingDirectoryIsUNC; // internal for unit testing private string _batchFile; @@ -84,9 +82,6 @@ public string Command public bool IgnoreExitCode { get; set; } - /// - public TaskEnvironment TaskEnvironment { get; set; } - /// /// Enable the pipe of the standard out to an item (StandardOutput). /// @@ -463,12 +458,10 @@ protected override bool ValidateParameters() } // determine what the working directory for the exec command is going to be -- if the user specified a working - // directory use that, otherwise default to the project directory (TaskEnvironment.ProjectDirectory). Using the - // project directory instead of the process current directory is important for correctness in multithreaded (/mt) - // builds, where the process working directory may not match the project being built. + // directory use that, otherwise it's the current directory _workingDirectory = !string.IsNullOrEmpty(WorkingDirectory) - ? TaskEnvironment.GetAbsolutePath(WorkingDirectory) - : TaskEnvironment.ProjectDirectory; + ? WorkingDirectory + : Directory.GetCurrentDirectory(); // check if the working directory we're going to use for the exec command is a UNC path workingDirectoryIsUNC = FileUtilitiesRegex.StartsWithUncPattern(_workingDirectory); @@ -477,7 +470,7 @@ protected override bool ValidateParameters() // will not be able to auto-map to the UNC path if (workingDirectoryIsUNC && NativeMethods.AllDrivesMapped()) { - Log.LogErrorWithCodeFromResources("Exec.AllDriveLettersMappedError", _workingDirectory.OriginalValue); + Log.LogErrorWithCodeFromResources("Exec.AllDriveLettersMappedError", _workingDirectory); return false; } @@ -540,7 +533,7 @@ protected override string GetWorkingDirectory() // So verify it's valid here. if (!FileSystems.Default.DirectoryExists(_workingDirectory)) { - throw new DirectoryNotFoundException(ResourceUtilities.FormatResourceStringStripCodeAndKeyword("Exec.InvalidWorkingDirectory", _workingDirectory.OriginalValue)); + throw new DirectoryNotFoundException(ResourceUtilities.FormatResourceStringStripCodeAndKeyword("Exec.InvalidWorkingDirectory", _workingDirectory)); } if (workingDirectoryIsUNC) @@ -567,59 +560,6 @@ internal string GetWorkingDirectoryAccessor() return GetWorkingDirectory(); } - /// - /// Gets the ProcessStartInfo for the spawned process, with environment variables from TaskEnvironment. - /// In multithreaded mode, TaskEnvironment contains the virtualized environment for this project, - /// which must be passed to the spawned process since it won't inherit it from the (shared) process environment. - /// - protected override ProcessStartInfo GetProcessStartInfo( - string pathToTool, - string commandLineCommands, - string responseFileSwitch) - { - // Get the base ProcessStartInfo with all ToolTask settings (command line, redirections, encodings, etc.) - // This also applies EnvironmentVariables overrides from the task property. - ProcessStartInfo startInfo = base.GetProcessStartInfo(pathToTool, commandLineCommands, responseFileSwitch); - - // Replace the inherited process environment with the virtualized one from TaskEnvironment. - // TaskEnvironment.GetProcessStartInfo() already configures env vars and working directory correctly - // for both multithreaded (virtualized) and multi-process (inherited) modes. - ProcessStartInfo taskEnvStartInfo = TaskEnvironment.GetProcessStartInfo(); - startInfo.Environment.Clear(); - foreach (var kvp in taskEnvStartInfo.Environment) - { - startInfo.Environment[kvp.Key] = kvp.Value; - } - - // Re-apply obsolete EnvironmentOverride and EnvironmentVariables property overrides — - // they should take precedence over TaskEnvironment. The base class already applied these, - // but we cleared the environment above, so we need to re-apply them. -#pragma warning disable 0618 // obsolete - Dictionary envOverrides = EnvironmentOverride; - if (envOverrides != null) - { - foreach (KeyValuePair entry in envOverrides) - { - startInfo.Environment[entry.Key] = entry.Value; - } - } -#pragma warning restore 0618 - - if (EnvironmentVariables != null) - { - foreach (string entry in EnvironmentVariables) - { - string[] nameValuePair = entry.Split(['='], 2); - if (nameValuePair.Length == 2 && nameValuePair[0].Length > 0) - { - startInfo.Environment[nameValuePair[0]] = nameValuePair[1]; - } - } - } - - return startInfo; - } - /// /// Adds the arguments for cmd.exe /// diff --git a/src/UnitTests.Shared/TaskEnvironmentHelper.cs b/src/UnitTests.Shared/TaskEnvironmentHelper.cs index 2a13fcfc288..484a5c917a9 100644 --- a/src/UnitTests.Shared/TaskEnvironmentHelper.cs +++ b/src/UnitTests.Shared/TaskEnvironmentHelper.cs @@ -20,24 +20,5 @@ public static TaskEnvironment CreateForTest() { return new TaskEnvironment(MultiProcessTaskEnvironmentDriver.Instance); } - - /// - /// Creates a TaskEnvironment backed by the multi-threaded driver which virtualizes - /// environment variables and current directory. This allows testing of multithreaded mode - /// behavior where each project has its own isolated environment. - /// - /// The project directory to use for the task environment. - /// A TaskEnvironment suitable for testing multithreaded mode scenarios. - /// - /// The caller is responsible for disposing the TaskEnvironment via TaskEnvironment.Dispose(), - /// which will clean up the underlying driver's thread-local state. - /// - // CA2000 is suppressed because the driver is owned by the TaskEnvironment and disposed via TaskEnvironment.Dispose() -#pragma warning disable CA2000 - public static TaskEnvironment CreateMultithreadedForTest(string projectDirectory) - { - return new TaskEnvironment(new MultiThreadedTaskEnvironmentDriver(projectDirectory)); - } -#pragma warning restore CA2000 } }