From 02bcc362eb10cd3be343cc719f87cab690b92b45 Mon Sep 17 00:00:00 2001 From: Jim Park Date: Fri, 27 Mar 2026 14:22:45 -0400 Subject: [PATCH 1/6] =?UTF-8?q?=EF=BB=BFFix=20unresolved=20P2P=20reference?= =?UTF-8?q?s=20losing=20Configuration=20in=20solution=20builds=20(ChangeWa?= =?UTF-8?q?ve=2018.6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- documentation/wiki/ChangeWaves.md | 1 + ...ignProjectConfigurationChangeWave_Tests.cs | 207 ++++++++++++++++++ src/Tasks/AssignProjectConfiguration.cs | 3 +- 3 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 src/Tasks.UnitTests/AssignProjectConfigurationChangeWave_Tests.cs diff --git a/documentation/wiki/ChangeWaves.md b/documentation/wiki/ChangeWaves.md index 27854c3cc9a..6e6727e9667 100644 --- a/documentation/wiki/ChangeWaves.md +++ b/documentation/wiki/ChangeWaves.md @@ -34,6 +34,7 @@ Change wave checks around features will be removed in the release that accompani - [TaskHostTask forwards request-level global properties (e.g. MSBuildRestoreSessionId) to out-of-proc TaskHost in -mt mode](https://github.com/dotnet/msbuild/pull/13443) - [Fix ShouldTreatWarningAsError in OOP TaskHost checking wrong collection (WarningsAsMessages instead of WarningsAsErrors)](https://github.com/dotnet/msbuild/issues/11952) - [Fix ToolTask hang when tool spawns grandchild processes that inherit stdout/stderr pipe handles](https://github.com/dotnet/msbuild/issues/2981) +- [Unresolved project references in solution builds inherit parent Configuration and Platform instead of stripping them via GlobalPropertiesToRemove](https://github.com/dotnet/msbuild/issues/13453) ### 18.5 - [FindUnderPath and AssignTargetPath tasks no longer throw on invalid path characters when using TaskEnvironment.GetAbsolutePath](https://github.com/dotnet/msbuild/pull/13069) diff --git a/src/Tasks.UnitTests/AssignProjectConfigurationChangeWave_Tests.cs b/src/Tasks.UnitTests/AssignProjectConfigurationChangeWave_Tests.cs new file mode 100644 index 00000000000..5106c9cc825 --- /dev/null +++ b/src/Tasks.UnitTests/AssignProjectConfigurationChangeWave_Tests.cs @@ -0,0 +1,207 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections; +using Microsoft.Build.Framework; +using Microsoft.Build.Shared; +using Microsoft.Build.Tasks; +using Microsoft.Build.Utilities; +using Shouldly; +using Xunit; +using Xunit.Abstractions; + +#nullable disable + +namespace Microsoft.Build.UnitTests +{ + /// + /// Tests for the ChangeWave 18.6 behavior change in AssignProjectConfiguration: + /// When project references are not found in the solution configuration blob, + /// they should inherit the parent's Configuration and Platform rather than + /// having them stripped via GlobalPropertiesToRemove. + /// + public sealed class AssignProjectConfigurationChangeWave_Tests + { + private readonly ITestOutputHelper _output; + + public AssignProjectConfigurationChangeWave_Tests(ITestOutputHelper output) + { + _output = output; + } + + /// + /// Verifies that when ShouldUnsetParentConfigurationAndPlatform is true, + /// unresolved project references do NOT get GlobalPropertiesToRemove=Configuration;Platform + /// set on them. This ensures child projects inherit the parent's Configuration and Platform + /// rather than falling back to their defaults (which breaks non-standard configuration names + /// like "Debug Unicode"). + /// + [Fact] + public void UnresolvedReferencesDoNotStripConfigurationUnderChangeWave() + { + using TestEnvironment env = TestEnvironment.Create(_output); + + var projectRefs = new ArrayList(); + // This reference is NOT in the solution config → will be unresolved + var unresolvedRef = ResolveNonMSBuildProjectOutput_Tests.CreateReferenceItem( + "Utility.vcxproj", + "{AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA}", + "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}", + "Utility"); + projectRefs.Add(unresolvedRef); + + // Solution config only contains a DIFFERENT project + var projectConfigurations = new Hashtable(); + projectConfigurations.Add("{BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB}", @"Debug Unicode|x64"); + + string xmlString = ResolveNonMSBuildProjectOutput_Tests.CreatePregeneratedPathDoc(projectConfigurations); + + MockEngine engine = new MockEngine(_output); + AssignProjectConfiguration task = new AssignProjectConfiguration(); + task.BuildEngine = engine; + task.SolutionConfigurationContents = xmlString; + task.ProjectReferences = (ITaskItem[])projectRefs.ToArray(typeof(ITaskItem)); + task.ShouldUnsetParentConfigurationAndPlatform = true; + + bool result = task.Execute(); + result.ShouldBeTrue(); + + // The reference should be unresolved (not in solution config) + task.UnassignedProjects.Length.ShouldBe(1); + task.AssignedProjects.Length.ShouldBe(0); + + // Under ChangeWave 18.6, GlobalPropertiesToRemove should NOT include Configuration;Platform + ITaskItem unresolved = task.UnassignedProjects[0]; + string globalPropertiesToRemove = unresolved.GetMetadata("GlobalPropertiesToRemove"); + globalPropertiesToRemove.ShouldNotContain("Configuration"); + globalPropertiesToRemove.ShouldNotContain("Platform"); + } + + /// + /// Verifies that when ShouldUnsetParentConfigurationAndPlatform is false, + /// unresolved references never had GlobalPropertiesToRemove set (this behavior + /// is unchanged by the ChangeWave). + /// + [Fact] + public void UnresolvedReferencesWithoutShouldUnsetDoNotStripConfiguration() + { + using TestEnvironment env = TestEnvironment.Create(_output); + + var projectRefs = new ArrayList(); + var unresolvedRef = ResolveNonMSBuildProjectOutput_Tests.CreateReferenceItem( + "Utility.vcxproj", + "{AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA}", + "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}", + "Utility"); + projectRefs.Add(unresolvedRef); + + var projectConfigurations = new Hashtable(); + projectConfigurations.Add("{BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB}", @"Debug Unicode|x64"); + + string xmlString = ResolveNonMSBuildProjectOutput_Tests.CreatePregeneratedPathDoc(projectConfigurations); + + MockEngine engine = new MockEngine(_output); + AssignProjectConfiguration task = new AssignProjectConfiguration(); + task.BuildEngine = engine; + task.SolutionConfigurationContents = xmlString; + task.ProjectReferences = (ITaskItem[])projectRefs.ToArray(typeof(ITaskItem)); + task.ShouldUnsetParentConfigurationAndPlatform = false; // default for non-solution builds + + bool result = task.Execute(); + result.ShouldBeTrue(); + + task.UnassignedProjects.Length.ShouldBe(1); + ITaskItem unresolved = task.UnassignedProjects[0]; + string globalPropertiesToRemove = unresolved.GetMetadata("GlobalPropertiesToRemove"); + globalPropertiesToRemove.ShouldBeEmpty(); + } + + /// + /// Verifies that resolved references still get correct SetConfiguration and SetPlatform + /// metadata with configurations containing spaces. + /// + [Fact] + public void ResolvedReferencesPreserveSpacesInConfigurationName() + { + using TestEnvironment env = TestEnvironment.Create(_output); + + var projectRefs = new ArrayList(); + var resolvedRef = ResolveNonMSBuildProjectOutput_Tests.CreateReferenceItem( + "Main.vcxproj", + "{CCCCCCCC-CCCC-CCCC-CCCC-CCCCCCCCCCCC}", + "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}", + "Main"); + projectRefs.Add(resolvedRef); + + // Solution config contains this project with a space in config name + var projectConfigurations = new Hashtable(); + projectConfigurations.Add("{CCCCCCCC-CCCC-CCCC-CCCC-CCCCCCCCCCCC}", @"Debug Unicode|x64"); + + string xmlString = ResolveNonMSBuildProjectOutput_Tests.CreatePregeneratedPathDoc(projectConfigurations); + + MockEngine engine = new MockEngine(_output); + AssignProjectConfiguration task = new AssignProjectConfiguration(); + task.BuildEngine = engine; + task.SolutionConfigurationContents = xmlString; + task.ProjectReferences = (ITaskItem[])projectRefs.ToArray(typeof(ITaskItem)); + task.ShouldUnsetParentConfigurationAndPlatform = true; + + bool result = task.Execute(); + result.ShouldBeTrue(); + + task.AssignedProjects.Length.ShouldBe(1); + task.UnassignedProjects.Length.ShouldBe(0); + + ITaskItem resolved = task.AssignedProjects[0]; + resolved.GetMetadata("SetConfiguration").ShouldBe("Configuration=Debug Unicode"); + resolved.GetMetadata("SetPlatform").ShouldBe("Platform=x64"); + resolved.GetMetadata("Configuration").ShouldBe("Debug Unicode"); + resolved.GetMetadata("Platform").ShouldBe("x64"); + } + + /// + /// Verifies that existing GlobalPropertiesToRemove metadata on an unresolved reference + /// is preserved (not appended to) under ChangeWave 18.6. + /// + [Fact] + public void ExistingGlobalPropertiesToRemovePreservedForUnresolvedReferences() + { + using TestEnvironment env = TestEnvironment.Create(_output); + + var projectRefs = new ArrayList(); + var unresolvedRef = ResolveNonMSBuildProjectOutput_Tests.CreateReferenceItem( + "Utility.vcxproj", + "{AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA}", + "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}", + "Utility"); + // Set some pre-existing GlobalPropertiesToRemove + unresolvedRef.SetMetadata("GlobalPropertiesToRemove", "TargetFramework"); + projectRefs.Add(unresolvedRef); + + var projectConfigurations = new Hashtable(); + projectConfigurations.Add("{BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB}", @"Debug|x64"); + + string xmlString = ResolveNonMSBuildProjectOutput_Tests.CreatePregeneratedPathDoc(projectConfigurations); + + MockEngine engine = new MockEngine(_output); + AssignProjectConfiguration task = new AssignProjectConfiguration(); + task.BuildEngine = engine; + task.SolutionConfigurationContents = xmlString; + task.ProjectReferences = (ITaskItem[])projectRefs.ToArray(typeof(ITaskItem)); + task.ShouldUnsetParentConfigurationAndPlatform = true; + + bool result = task.Execute(); + result.ShouldBeTrue(); + + task.UnassignedProjects.Length.ShouldBe(1); + ITaskItem unresolved = task.UnassignedProjects[0]; + string globalPropertiesToRemove = unresolved.GetMetadata("GlobalPropertiesToRemove"); + + // The pre-existing TargetFramework should still be there + globalPropertiesToRemove.ShouldContain("TargetFramework"); + // But Configuration;Platform should NOT be appended + globalPropertiesToRemove.ShouldNotContain("Configuration"); + globalPropertiesToRemove.ShouldNotContain("Platform"); + } + } +} diff --git a/src/Tasks/AssignProjectConfiguration.cs b/src/Tasks/AssignProjectConfiguration.cs index ba5062c1ce5..c12b80ea25b 100644 --- a/src/Tasks/AssignProjectConfiguration.cs +++ b/src/Tasks/AssignProjectConfiguration.cs @@ -208,7 +208,8 @@ public override bool Execute() // If the reference was unresolved, we want to undefine the Configuration and Platform // global properties, so that the project will build using its default Configuration and // Platform rather than that of its parent. - if (ShouldUnsetParentConfigurationAndPlatform) + if (ShouldUnsetParentConfigurationAndPlatform + && !ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave18_6)) { string globalPropertiesToRemove = projectRef.GetMetadata("GlobalPropertiesToRemove"); From 8576ac196d4849fb0c1c610f8e28e5d8ab3900d1 Mon Sep 17 00:00:00 2001 From: jimpark Date: Fri, 27 Mar 2026 18:25:31 -0400 Subject: [PATCH 2/6] Update src/Tasks/AssignProjectConfiguration.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Tasks/AssignProjectConfiguration.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Tasks/AssignProjectConfiguration.cs b/src/Tasks/AssignProjectConfiguration.cs index c12b80ea25b..0f22e291956 100644 --- a/src/Tasks/AssignProjectConfiguration.cs +++ b/src/Tasks/AssignProjectConfiguration.cs @@ -205,9 +205,10 @@ public override bool Execute() } else { - // If the reference was unresolved, we want to undefine the Configuration and Platform - // global properties, so that the project will build using its default Configuration and - // Platform rather than that of its parent. + // Pre-18.6 behavior: for unresolved references, undefine the Configuration and Platform + // global properties so that the referenced project builds using its own default + // Configuration and Platform rather than inheriting those from its parent. Under ChangeWave + // 18.6, this block is skipped and unresolved references keep the parent's Configuration/Platform. if (ShouldUnsetParentConfigurationAndPlatform && !ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave18_6)) { From ba4fdfb66bf598a56bdb567c8dbbcefb542b832a Mon Sep 17 00:00:00 2001 From: Jim Park Date: Fri, 27 Mar 2026 18:58:21 -0400 Subject: [PATCH 3/6] Add PR requested tests --- ...ignProjectConfigurationChangeWave_Tests.cs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/Tasks.UnitTests/AssignProjectConfigurationChangeWave_Tests.cs b/src/Tasks.UnitTests/AssignProjectConfigurationChangeWave_Tests.cs index 5106c9cc825..98c7f138cf8 100644 --- a/src/Tasks.UnitTests/AssignProjectConfigurationChangeWave_Tests.cs +++ b/src/Tasks.UnitTests/AssignProjectConfigurationChangeWave_Tests.cs @@ -77,6 +77,53 @@ public void UnresolvedReferencesDoNotStripConfigurationUnderChangeWave() globalPropertiesToRemove.ShouldNotContain("Platform"); } + /// + /// Verifies that when ChangeWave 18.6 is disabled (opted out), unresolved references + /// DO get GlobalPropertiesToRemove=Configuration;Platform set on them — the pre-18.6 behavior. + /// + [Fact] + public void UnresolvedReferencesStripConfigurationWhenChangeWaveDisabled() + { + using TestEnvironment env = TestEnvironment.Create(_output); + + // Disable ChangeWave 18.6 to get the old behavior + ChangeWaves.ResetStateForTests(); + env.SetEnvironmentVariable("MSBUILDDISABLEFEATURESFROMVERSION", ChangeWaves.Wave18_6.ToString()); + BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(); + + var projectRefs = new ArrayList(); + var unresolvedRef = ResolveNonMSBuildProjectOutput_Tests.CreateReferenceItem( + "Utility.vcxproj", + "{AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA}", + "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}", + "Utility"); + projectRefs.Add(unresolvedRef); + + var projectConfigurations = new Hashtable(); + projectConfigurations.Add("{BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB}", @"Debug Unicode|x64"); + + string xmlString = ResolveNonMSBuildProjectOutput_Tests.CreatePregeneratedPathDoc(projectConfigurations); + + MockEngine engine = new MockEngine(_output); + AssignProjectConfiguration task = new AssignProjectConfiguration(); + task.BuildEngine = engine; + task.SolutionConfigurationContents = xmlString; + task.ProjectReferences = (ITaskItem[])projectRefs.ToArray(typeof(ITaskItem)); + task.ShouldUnsetParentConfigurationAndPlatform = true; + + bool result = task.Execute(); + result.ShouldBeTrue(); + + task.UnassignedProjects.Length.ShouldBe(1); + ITaskItem unresolved = task.UnassignedProjects[0]; + string globalPropertiesToRemove = unresolved.GetMetadata("GlobalPropertiesToRemove"); + + // With ChangeWave 18.6 disabled, the old behavior should apply: + // Configuration;Platform should be appended to GlobalPropertiesToRemove + globalPropertiesToRemove.ShouldContain("Configuration"); + globalPropertiesToRemove.ShouldContain("Platform"); + } + /// /// Verifies that when ShouldUnsetParentConfigurationAndPlatform is false, /// unresolved references never had GlobalPropertiesToRemove set (this behavior From 5c2f7b9530534a3beb304f87f9281cd399c2f385 Mon Sep 17 00:00:00 2001 From: Jim Park Date: Sat, 28 Mar 2026 08:59:32 -0400 Subject: [PATCH 4/6] Trigger CI rerun From 678fef89acf900fc81e5d899cd85aa21babba308 Mon Sep 17 00:00:00 2001 From: Jim Park Date: Sat, 28 Mar 2026 23:53:33 -0400 Subject: [PATCH 5/6] Retry CI From 14c5e3307525713a9a76bcb45f656889363198ee Mon Sep 17 00:00:00 2001 From: Jim Park Date: Tue, 31 Mar 2026 16:45:17 -0400 Subject: [PATCH 6/6] Add stronger tests --- ...ignProjectConfigurationChangeWave_Tests.cs | 213 ++++++++++++++++++ 1 file changed, 213 insertions(+) diff --git a/src/Tasks.UnitTests/AssignProjectConfigurationChangeWave_Tests.cs b/src/Tasks.UnitTests/AssignProjectConfigurationChangeWave_Tests.cs index 98c7f138cf8..5c8334a56a1 100644 --- a/src/Tasks.UnitTests/AssignProjectConfigurationChangeWave_Tests.cs +++ b/src/Tasks.UnitTests/AssignProjectConfigurationChangeWave_Tests.cs @@ -1,7 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections; +using System.Collections.Generic; +using System.IO; using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Tasks; @@ -250,5 +253,215 @@ public void ExistingGlobalPropertiesToRemovePreservedForUnresolvedReferences() globalPropertiesToRemove.ShouldNotContain("Configuration"); globalPropertiesToRemove.ShouldNotContain("Platform"); } + + /// + /// End-to-end reproduction of issue #13453 (pre-18.6 behavior): + /// + /// In a solution build, Configuration flows as an inherited global property from the + /// command line (/p:Configuration="Debug Unicode") through the solution metaproject to + /// each project. For unresolved project references (not found in the .sln), the old + /// AssignProjectConfiguration behavior adds "Configuration;Platform" to + /// GlobalPropertiesToRemove. Microsoft.Common.CurrentVersion.targets then passes this + /// as RemoveProperties="%(GlobalPropertiesToRemove)" to the MSBuild task. The MSBuild + /// task strips Configuration from the child's inherited global properties, causing it + /// to fall back to its default ("Debug") instead of the parent's "Debug Unicode". + /// + /// This test demonstrates the full causal chain: + /// 1. AssignProjectConfiguration sets GlobalPropertiesToRemove=Configuration;Platform + /// 2. A parent project built with /p:Configuration="Debug Unicode" calls MSBuild on + /// a child project with RemoveProperties=Configuration;Platform + /// 3. The child project loses "Debug Unicode" and falls back to its default "Debug" + /// + [Fact] + public void Issue13453_OldBehavior_UnresolvedReference_ChildLosesSpacedConfiguration() + { + using TestEnvironment env = TestEnvironment.Create(_output); + + // Disable ChangeWave 18.6 to reproduce the old (buggy) behavior + ChangeWaves.ResetStateForTests(); + env.SetEnvironmentVariable("MSBUILDDISABLEFEATURESFROMVERSION", ChangeWaves.Wave18_6.ToString()); + BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(); + + // --- Step 1: Run AssignProjectConfiguration with an unresolved reference --- + var projectRefs = new ArrayList(); + var unresolvedRef = ResolveNonMSBuildProjectOutput_Tests.CreateReferenceItem( + "Utility.vcxproj", + "{AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA}", + "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}", + "Utility"); + projectRefs.Add(unresolvedRef); + + // Solution config does NOT contain this project's GUID — it will be unresolved + var projectConfigurations = new Hashtable(); + projectConfigurations.Add("{BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB}", @"Debug Unicode|x64"); + + string xmlString = ResolveNonMSBuildProjectOutput_Tests.CreatePregeneratedPathDoc(projectConfigurations); + + MockEngine assignEngine = new MockEngine(_output); + AssignProjectConfiguration assignTask = new AssignProjectConfiguration(); + assignTask.BuildEngine = assignEngine; + assignTask.SolutionConfigurationContents = xmlString; + assignTask.ProjectReferences = (ITaskItem[])projectRefs.ToArray(typeof(ITaskItem)); + assignTask.ShouldUnsetParentConfigurationAndPlatform = true; + + bool assignResult = assignTask.Execute(); + assignResult.ShouldBeTrue(); + assignTask.UnassignedProjects.Length.ShouldBe(1); + + // The old behavior: GlobalPropertiesToRemove includes Configuration;Platform + ITaskItem unresolved = assignTask.UnassignedProjects[0]; + string globalPropertiesToRemove = unresolved.GetMetadata("GlobalPropertiesToRemove"); + globalPropertiesToRemove.ShouldContain("Configuration"); + globalPropertiesToRemove.ShouldContain("Platform"); + + // --- Step 2: Demonstrate the consequence of that metadata. + // A parent project built with Configuration=Debug Unicode calls MSBuild on + // itself (child target) with RemoveProperties=Configuration;Platform. + // This mirrors the solution build pipeline in + // Microsoft.Common.CurrentVersion.targets: + // + string projectFile = ObjectModelHelpers.CreateTempFileOnDisk(@" + + + Debug + + + + + + + + + "); + + try + { + // Build with Configuration=Debug Unicode as a global property, + // simulating: msbuild /p:Configuration="Debug Unicode" /p:Platform=x64 + var globalProps = new Dictionary + { + { "Configuration", "Debug Unicode" }, + { "Platform", "x64" } + }; + + MockLogger logger = new MockLogger(_output); + bool buildResult = ObjectModelHelpers.BuildTempProjectFileWithTargets( + projectFile, new[] { "ParentBuild" }, globalProps, logger); + buildResult.ShouldBeTrue(); + + _output.WriteLine(logger.FullLog); + + // The child target received Configuration=Debug (the default) because + // RemoveProperties=Configuration;Platform stripped the inherited + // "Debug Unicode" global property. This is the bug: the user passed + // Configuration="Debug Unicode" but the child project sees "Debug". + logger.AssertLogContains("ChildConfiguration=[Debug]"); + logger.AssertLogDoesntContain("ChildConfiguration=[Debug Unicode]"); + } + finally + { + File.Delete(projectFile); + } + } + + /// + /// End-to-end verification that ChangeWave 18.6 fixes issue #13453: + /// + /// With the fix, AssignProjectConfiguration no longer adds "Configuration;Platform" + /// to GlobalPropertiesToRemove for unresolved references. Without RemoveProperties, + /// the MSBuild task preserves the parent's inherited Configuration="Debug Unicode" + /// when building the child project. + /// + /// This test demonstrates the fixed causal chain: + /// 1. AssignProjectConfiguration does NOT set GlobalPropertiesToRemove + /// 2. A parent project built with /p:Configuration="Debug Unicode" calls MSBuild on + /// a child project with RemoveProperties="" (empty) + /// 3. The child project correctly inherits "Debug Unicode" + /// + [Fact] + public void Issue13453_NewBehavior_UnresolvedReference_ChildInheritsSpacedConfiguration() + { + using TestEnvironment env = TestEnvironment.Create(_output); + + // ChangeWave 18.6 is enabled by default — this is the fixed behavior + + // --- Step 1: Run AssignProjectConfiguration with an unresolved reference --- + var projectRefs = new ArrayList(); + var unresolvedRef = ResolveNonMSBuildProjectOutput_Tests.CreateReferenceItem( + "Utility.vcxproj", + "{AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA}", + "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}", + "Utility"); + projectRefs.Add(unresolvedRef); + + // Solution config does NOT contain this project's GUID — it will be unresolved + var projectConfigurations = new Hashtable(); + projectConfigurations.Add("{BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB}", @"Debug Unicode|x64"); + + string xmlString = ResolveNonMSBuildProjectOutput_Tests.CreatePregeneratedPathDoc(projectConfigurations); + + MockEngine assignEngine = new MockEngine(_output); + AssignProjectConfiguration assignTask = new AssignProjectConfiguration(); + assignTask.BuildEngine = assignEngine; + assignTask.SolutionConfigurationContents = xmlString; + assignTask.ProjectReferences = (ITaskItem[])projectRefs.ToArray(typeof(ITaskItem)); + assignTask.ShouldUnsetParentConfigurationAndPlatform = true; + + bool assignResult = assignTask.Execute(); + assignResult.ShouldBeTrue(); + assignTask.UnassignedProjects.Length.ShouldBe(1); + + // The fix: GlobalPropertiesToRemove does NOT include Configuration;Platform + ITaskItem unresolved = assignTask.UnassignedProjects[0]; + string globalPropertiesToRemove = unresolved.GetMetadata("GlobalPropertiesToRemove"); + globalPropertiesToRemove.ShouldNotContain("Configuration"); + globalPropertiesToRemove.ShouldNotContain("Platform"); + + // --- Step 2: Demonstrate that without RemoveProperties, the child keeps + // "Debug Unicode". Same project as the old-behavior test, but WITHOUT + // RemoveProperties on the MSBuild task call. --- + string projectFile = ObjectModelHelpers.CreateTempFileOnDisk(@" + + + Debug + + + + + + + + + "); + + try + { + // Build with Configuration=Debug Unicode as a global property + var globalProps = new Dictionary + { + { "Configuration", "Debug Unicode" }, + { "Platform", "x64" } + }; + + MockLogger logger = new MockLogger(_output); + bool buildResult = ObjectModelHelpers.BuildTempProjectFileWithTargets( + projectFile, new[] { "ParentBuild" }, globalProps, logger); + buildResult.ShouldBeTrue(); + + _output.WriteLine(logger.FullLog); + + // The child correctly inherits "Debug Unicode" from the parent. + // This is the fix for issue #13453. + logger.AssertLogContains("ChildConfiguration=[Debug Unicode]"); + } + finally + { + File.Delete(projectFile); + } + } } }