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;