Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions documentation/wiki/ChangeWaves.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
207 changes: 207 additions & 0 deletions src/Tasks.UnitTests/AssignProjectConfigurationChangeWave_Tests.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// 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.
/// </summary>
public sealed class AssignProjectConfigurationChangeWave_Tests
{
private readonly ITestOutputHelper _output;

public AssignProjectConfigurationChangeWave_Tests(ITestOutputHelper output)
{
_output = output;
}

/// <summary>
/// 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").
/// </summary>
[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");
}

/// <summary>
/// Verifies that when ShouldUnsetParentConfigurationAndPlatform is false,
/// unresolved references never had GlobalPropertiesToRemove set (this behavior
/// is unchanged by the ChangeWave).
/// </summary>
[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();
}

/// <summary>
/// Verifies that resolved references still get correct SetConfiguration and SetPlatform
/// metadata with configurations containing spaces.
/// </summary>
[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");
}

/// <summary>
/// Verifies that existing GlobalPropertiesToRemove metadata on an unresolved reference
/// is preserved (not appended to) under ChangeWave 18.6.
/// </summary>
[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");
}
}
}
3 changes: 2 additions & 1 deletion src/Tasks/AssignProjectConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down
Loading