diff --git a/src/Build/Definition/ToolsetConfigurationReader.cs b/src/Build/Definition/ToolsetConfigurationReader.cs index 5665b1aaf09..990a8b686cb 100644 --- a/src/Build/Definition/ToolsetConfigurationReader.cs +++ b/src/Build/Definition/ToolsetConfigurationReader.cs @@ -10,6 +10,7 @@ using Microsoft.Build.Execution; using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; +using Microsoft.Build.Utilities; using ErrorUtilities = Microsoft.Build.Shared.ErrorUtilities; using InvalidToolsetDefinitionException = Microsoft.Build.Exceptions.InvalidToolsetDefinitionException; @@ -41,6 +42,13 @@ internal class ToolsetConfigurationReader : ToolsetReader /// private static readonly char[] s_separatorForExtensionsPathSearchPaths = MSBuildConstants.SemicolonChar; + /// + /// Caching MSBuild exe configuration. + /// Used only by ReadApplicationConfiguration factory function (default) as oppose to unit tests config factory functions + /// which must not cache configs. + /// + private static readonly Lazy s_configurationCache = new Lazy(ReadOpenMappedExeConfiguration); + /// /// Cached values of tools version -> project import search paths table /// @@ -250,6 +258,18 @@ protected override IEnumerable GetSubToolsetPropertyD /// Unit tests wish to avoid reading (nunit.exe) application configuration file. /// private static Configuration ReadApplicationConfiguration() + { + if (ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_0)) + { + return s_configurationCache.Value; + } + else + { + return ReadOpenMappedExeConfiguration(); + } + } + + private static Configuration ReadOpenMappedExeConfiguration() { // When running from the command-line or from VS, use the msbuild.exe.config file. if (BuildEnvironmentHelper.Instance.Mode != BuildEnvironmentMode.None && diff --git a/src/Shared/ToolsetElement.cs b/src/Shared/ToolsetElement.cs index ff7d9685aa7..9902fd49a06 100644 --- a/src/Shared/ToolsetElement.cs +++ b/src/Shared/ToolsetElement.cs @@ -7,6 +7,7 @@ using System.IO; using Microsoft.Build.Collections; using Microsoft.Build.Shared; +using Microsoft.Build.Utilities; namespace Microsoft.Build.Evaluation { @@ -15,7 +16,49 @@ namespace Microsoft.Build.Evaluation /// internal static class ToolsetConfigurationReaderHelpers { + /// + /// Lock for process wide ToolsetConfigurationSection section cache + /// + private static readonly object s_syncLock = new(); + + /// + /// Process wide ToolsetConfigurationSection section cache + /// + private static ToolsetConfigurationSection s_toolsetConfigurationSectionCache; + private static Configuration s_configurationOfCachedSection; + internal static ToolsetConfigurationSection ReadToolsetConfigurationSection(Configuration configuration) + { + if (ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_0)) + { + if (configuration == null) + { + return null; + } + + lock (s_syncLock) + { + // Cache 1st requested configuration section. In unit tests, different Configuration is provided for particular test cases. + // During runtime, however, only MSBuild exe configuration file is provided to read toolset configuration from, + // and modifying MSBuild exe configuration during lifetime of msbuild nodes is neither expected nor supported. + if (s_toolsetConfigurationSectionCache == null) + { + s_toolsetConfigurationSectionCache = GetToolsetConfigurationSection(configuration); + s_configurationOfCachedSection = configuration; + } + + return s_configurationOfCachedSection == configuration ? + s_toolsetConfigurationSectionCache : + GetToolsetConfigurationSection(configuration); + } + } + else + { + return GetToolsetConfigurationSection(configuration); + } + } + + private static ToolsetConfigurationSection GetToolsetConfigurationSection(Configuration configuration) { ToolsetConfigurationSection configurationSection = null;