diff --git a/documentation/wiki/ChangeWaves.md b/documentation/wiki/ChangeWaves.md index c09ef8f9bf9..66693a74667 100644 --- a/documentation/wiki/ChangeWaves.md +++ b/documentation/wiki/ChangeWaves.md @@ -24,6 +24,9 @@ A wave of features is set to "rotate out" (i.e. become standard functionality) t ## Current Rotation of Change Waves +### 18.4 +- [Start throwing on null or empty paths in MultiProcess and MultiThreaded Task Environment Drivers.](https://github.com/dotnet/msbuild/pull/12914) + ### 18.3 - [Replace Transactional property with ChangeWave control, implement atomic file replacement with retry logic, and update tests.](https://github.com/dotnet/msbuild/pull/12627) diff --git a/src/Build/BackEnd/TaskExecutionHost/MultiProcessTaskEnvironmentDriver.cs b/src/Build/BackEnd/TaskExecutionHost/MultiProcessTaskEnvironmentDriver.cs index f794bf64293..bec24b034f7 100644 --- a/src/Build/BackEnd/TaskExecutionHost/MultiProcessTaskEnvironmentDriver.cs +++ b/src/Build/BackEnd/TaskExecutionHost/MultiProcessTaskEnvironmentDriver.cs @@ -4,11 +4,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -#if NETFRAMEWORK -using Microsoft.IO; -#else -using System.IO; -#endif using Microsoft.Build.Framework; using Microsoft.Build.Internal; @@ -45,10 +40,13 @@ public AbsolutePath ProjectDirectory /// public AbsolutePath GetAbsolutePath(string path) { - // This function should not throw when path has illegal characters. - // For .NET Framework, Microsoft.IO.Path.Combine should be used instead of System.IO.Path.Combine to achieve it. - // For .NET Core, System.IO.Path.Combine already does not throw in this case. - return new AbsolutePath(Path.Combine(NativeMethodsShared.GetCurrentDirectory(), path), ignoreRootedCheck: true); + // Opt-out for null path when Wave18_4 is disabled - return null as-is. + if (!ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave18_4) && path is null) + { + return new AbsolutePath(path!, path!, ignoreRootedCheck: true); + } + + return new AbsolutePath(path, basePath: ProjectDirectory); } /// diff --git a/src/Build/BackEnd/TaskExecutionHost/MultiThreadedTaskEnvironmentDriver.cs b/src/Build/BackEnd/TaskExecutionHost/MultiThreadedTaskEnvironmentDriver.cs index cf54c430d0d..1bf9009054a 100644 --- a/src/Build/BackEnd/TaskExecutionHost/MultiThreadedTaskEnvironmentDriver.cs +++ b/src/Build/BackEnd/TaskExecutionHost/MultiThreadedTaskEnvironmentDriver.cs @@ -71,6 +71,12 @@ public AbsolutePath ProjectDirectory /// public AbsolutePath GetAbsolutePath(string path) { + // Opt-out for null path when Wave18_4 is disabled - return null as-is. + if (!ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave18_4) && path is null) + { + return new AbsolutePath(path!, path!, ignoreRootedCheck: true); + } + return new AbsolutePath(path, ProjectDirectory); } diff --git a/src/Framework/ChangeWaves.cs b/src/Framework/ChangeWaves.cs index 66ff885a352..22049fdfc65 100644 --- a/src/Framework/ChangeWaves.cs +++ b/src/Framework/ChangeWaves.cs @@ -31,7 +31,8 @@ internal static class ChangeWaves internal static readonly Version Wave17_12 = new Version(17, 12); internal static readonly Version Wave17_14 = new Version(17, 14); internal static readonly Version Wave18_3 = new Version(18, 3); - internal static readonly Version[] AllWaves = [Wave17_10, Wave17_12, Wave17_14, Wave18_3]; + internal static readonly Version Wave18_4 = new Version(18, 4); + internal static readonly Version[] AllWaves = [Wave17_10, Wave17_12, Wave17_14, Wave18_3, Wave18_4]; /// /// Special value indicating that all features behind all Change Waves should be enabled. diff --git a/src/Framework/PathHelpers/AbsolutePath.cs b/src/Framework/PathHelpers/AbsolutePath.cs index 1c5cc585e5b..7fb31c57232 100644 --- a/src/Framework/PathHelpers/AbsolutePath.cs +++ b/src/Framework/PathHelpers/AbsolutePath.cs @@ -19,7 +19,7 @@ namespace Microsoft.Build.Framework /// file system conventions (case-sensitive on Linux, case-insensitive on Windows and macOS). /// Does not perform any normalization beyond validating the path is fully qualified. /// A default instance (created via default(AbsolutePath)) has a null Value - /// and should not be used. Two default instances are considered equal. + /// and represents an issue in path handling. Two default instances are considered equal. /// public readonly struct AbsolutePath : IEquatable { @@ -32,6 +32,11 @@ namespace Microsoft.Build.Framework /// The normalized string representation of this path. /// public string Value { get; } + + /// + /// The original string used to create this path. + /// + public string OriginalValue { get; } /// /// Initializes a new instance of the struct. @@ -41,6 +46,7 @@ public AbsolutePath(string path) { ValidatePath(path); Value = path; + OriginalValue = path; } /// @@ -50,12 +56,24 @@ public AbsolutePath(string path) /// If true, skips checking whether the path is rooted. /// For internal and testing use, when we want to force bypassing the rooted check. internal AbsolutePath(string path, bool ignoreRootedCheck) + : this(path, path, ignoreRootedCheck) { - if (!ignoreRootedCheck) + } + + /// + /// Initializes a new instance of the struct. + /// + /// The absolute path string. + /// The original string used to create this path. + /// If true, skips checking whether the path is rooted. + internal AbsolutePath(string path, string original, bool ignoreRootedCheck) + { + if (!ignoreRootedCheck) { ValidatePath(path); } Value = path; + OriginalValue = original; } /// @@ -85,7 +103,14 @@ private static void ValidatePath(string path) /// /// The path to combine with the base path. /// The base path to combine with. - public AbsolutePath(string path, AbsolutePath basePath) => Value = Path.Combine(basePath.Value, path); + public AbsolutePath(string path, AbsolutePath basePath) + { + // This function should not throw when path has illegal characters. + // For .NET Framework, Microsoft.IO.Path.Combine should be used instead of System.IO.Path.Combine to achieve it. + // For .NET Core, System.IO.Path.Combine already does not throw in this case. + Value = Path.Combine(basePath.Value, path); + OriginalValue = path; + } /// /// Implicitly converts an AbsolutePath to a string.