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
}
}