From 7874ecfdf9c780b6b2ed43c12cce48a8facebbee Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Fri, 10 Oct 2025 10:21:21 -0500 Subject: [PATCH 1/4] rewrite resolveprojectreferences MSBuild calls to reduce the amount of batching done --- src/Tasks/MSBuild.cs | 19 +++--- .../Microsoft.Common.CurrentVersion.targets | 67 ++++++++++++------- 2 files changed, 49 insertions(+), 37 deletions(-) diff --git a/src/Tasks/MSBuild.cs b/src/Tasks/MSBuild.cs index d3c24bd0f1d..2b505528928 100644 --- a/src/Tasks/MSBuild.cs +++ b/src/Tasks/MSBuild.cs @@ -58,19 +58,16 @@ private enum SkipNonExistentProjectsBehavior /// /// A list of property name/value pairs to apply as global properties to /// the child project. - /// A typical input: "propname1=propvalue1", "propname2=propvalue2", "propname3=propvalue3". + /// A typical input would look like this: + /// Properties="propname1=propvalue1;propname2=propvalue2;propname3=propvalue3" /// /// - /// ` - /// The engine fails on this because it doesn't like item lists being concatenated with string - /// constants when the data is being passed into an array parameter. So the workaround is to - /// write this in the project file: - /// `` - /// ]]> - /// + /// The fact that this is a string[] makes the following illegal: + /// ]]> + /// The engine fails on this because it doesn't like item lists being concatenated with string + /// constants when the data is being passed into an array parameter. So the workaround is to + /// write this in the project file: + /// ]]> /// public string[] Properties { get; set; } diff --git a/src/Tasks/Microsoft.Common.CurrentVersion.targets b/src/Tasks/Microsoft.Common.CurrentVersion.targets index 4db3d552c7e..36b76d51f9a 100644 --- a/src/Tasks/Microsoft.Common.CurrentVersion.targets +++ b/src/Tasks/Microsoft.Common.CurrentVersion.targets @@ -2128,58 +2128,73 @@ Copyright (C) Microsoft Corporation. All rights reserved. much information as possible will be passed to the compilers. --> + + <_ProjectsForGetTargetPath Include="@(_MSBuildProjectReferenceExistent)" + Condition="'%(_MSBuildProjectReferenceExistent.BuildReference)' == 'true' and '@(ProjectReferenceWithConfiguration)' != '' and ('$(BuildingInsideVisualStudio)' == 'true' or '$(BuildProjectReferences)' != 'true') and '$(VisualStudioVersion)' != '10.0' and '@(_MSBuildProjectReferenceExistent)' != ''"> + %(_MSBuildProjectReferenceExistent.SetConfiguration); %(_MSBuildProjectReferenceExistent.SetPlatform); %(_MSBuildProjectReferenceExistent.SetTargetFramework) + %(_MSBuildProjectReferenceExistent.GlobalPropertiesToRemove)$(_GlobalPropertiesToRemoveFromProjectReferences) + + + - + ContinueOnError="!$(BuildingProject)"> + Condition="'%(_ProjectsForGetTargetPath.ReferenceOutputAssembly)'=='true'" /> - + ItemName="%(_ProjectsForGetTargetPath.OutputItemType)" + Condition="'%(_ProjectsForGetTargetPath.OutputItemType)' != ''" /> + + <_ProjectsForCommandLineBuild Include="@(_MSBuildProjectReferenceExistent)" + Condition="'%(_MSBuildProjectReferenceExistent.BuildReference)' == 'true' and '@(ProjectReferenceWithConfiguration)' != '' and '$(BuildingInsideVisualStudio)' != 'true' and '$(BuildProjectReferences)' == 'true' and '@(_MSBuildProjectReferenceExistent)' != ''"> + %(_MSBuildProjectReferenceExistent.SetConfiguration); %(_MSBuildProjectReferenceExistent.SetPlatform); %(_MSBuildProjectReferenceExistent.SetTargetFramework) + %(_MSBuildProjectReferenceExistent.GlobalPropertiesToRemove)$(_GlobalPropertiesToRemoveFromProjectReferences) + %(_MSBuildProjectReferenceExistent.Targets) + + + + - + ContinueOnError="$(ContinueOnError)"> + Condition="'%(_ProjectsForCommandLineBuild.ReferenceOutputAssembly)'=='true' or '$(DesignTimeBuild)' == 'true'" /> + ItemName="%(_ProjectsForCommandLineBuild.OutputItemType)" + Condition="'%(_ProjectsForCommandLineBuild.OutputItemType)' != ''" /> + + <_ProjectsForNativeManifest Include="@(_MSBuildProjectReferenceExistent)" + Condition="'%(_MSBuildProjectReferenceExistent.BuildReference)' == 'true' and '@(ProjectReferenceWithConfiguration)' != '' and '$(BuildingProject)' == 'true' and '@(_MSBuildProjectReferenceExistent)' != ''"> + %(_MSBuildProjectReferenceExistent.SetConfiguration); %(_MSBuildProjectReferenceExistent.SetPlatform); %(_MSBuildProjectReferenceExistent.SetTargetFramework) + %(_MSBuildProjectReferenceExistent.GlobalPropertiesToRemove)$(_GlobalPropertiesToRemoveFromProjectReferences) + + + + SkipNonexistentTargets="true"> + Condition="'%(_ProjectsForNativeManifest.ReferenceOutputAssembly)' == 'true'" /> From cd810e412a8221ecc81be36111b9921ab912a85f Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Fri, 10 Oct 2025 14:25:10 -0500 Subject: [PATCH 2/4] maybe fix some msbuild syntax issues --- .../Microsoft.Common.CurrentVersion.targets | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/Tasks/Microsoft.Common.CurrentVersion.targets b/src/Tasks/Microsoft.Common.CurrentVersion.targets index 36b76d51f9a..7152eb3065f 100644 --- a/src/Tasks/Microsoft.Common.CurrentVersion.targets +++ b/src/Tasks/Microsoft.Common.CurrentVersion.targets @@ -2130,10 +2130,9 @@ Copyright (C) Microsoft Corporation. All rights reserved. <_ProjectsForGetTargetPath Include="@(_MSBuildProjectReferenceExistent)" - Condition="'%(_MSBuildProjectReferenceExistent.BuildReference)' == 'true' and '@(ProjectReferenceWithConfiguration)' != '' and ('$(BuildingInsideVisualStudio)' == 'true' or '$(BuildProjectReferences)' != 'true') and '$(VisualStudioVersion)' != '10.0' and '@(_MSBuildProjectReferenceExistent)' != ''"> - %(_MSBuildProjectReferenceExistent.SetConfiguration); %(_MSBuildProjectReferenceExistent.SetPlatform); %(_MSBuildProjectReferenceExistent.SetTargetFramework) - %(_MSBuildProjectReferenceExistent.GlobalPropertiesToRemove)$(_GlobalPropertiesToRemoveFromProjectReferences) - + Condition="'%(_MSBuildProjectReferenceExistent.BuildReference)' == 'true' and '@(ProjectReferenceWithConfiguration)' != '' and ('$(BuildingInsideVisualStudio)' == 'true' or '$(BuildProjectReferences)' != 'true') and '$(VisualStudioVersion)' != '10.0' and '@(_MSBuildProjectReferenceExistent)' != ''" + Properties="%(_MSBuildProjectReferenceExistent.SetConfiguration); %(_MSBuildProjectReferenceExistent.SetPlatform); %(_MSBuildProjectReferenceExistent.SetTargetFramework)" + RemoveProperties="%(_MSBuildProjectReferenceExistent.GlobalPropertiesToRemove)$(_GlobalPropertiesToRemoveFromProjectReferences)" /> <_ProjectsForCommandLineBuild Include="@(_MSBuildProjectReferenceExistent)" - Condition="'%(_MSBuildProjectReferenceExistent.BuildReference)' == 'true' and '@(ProjectReferenceWithConfiguration)' != '' and '$(BuildingInsideVisualStudio)' != 'true' and '$(BuildProjectReferences)' == 'true' and '@(_MSBuildProjectReferenceExistent)' != ''"> - %(_MSBuildProjectReferenceExistent.SetConfiguration); %(_MSBuildProjectReferenceExistent.SetPlatform); %(_MSBuildProjectReferenceExistent.SetTargetFramework) - %(_MSBuildProjectReferenceExistent.GlobalPropertiesToRemove)$(_GlobalPropertiesToRemoveFromProjectReferences) - %(_MSBuildProjectReferenceExistent.Targets) - + Condition="'%(_MSBuildProjectReferenceExistent.BuildReference)' == 'true' and '@(ProjectReferenceWithConfiguration)' != '' and '$(BuildingInsideVisualStudio)' != 'true' and '$(BuildProjectReferences)' == 'true' and '@(_MSBuildProjectReferenceExistent)' != ''" + Properties="%(_MSBuildProjectReferenceExistent.SetConfiguration); %(_MSBuildProjectReferenceExistent.SetPlatform); %(_MSBuildProjectReferenceExistent.SetTargetFramework)" + RemoveProperties="%(_MSBuildProjectReferenceExistent.GlobalPropertiesToRemove)$(_GlobalPropertiesToRemoveFromProjectReferences)" + Targets="%(_MSBuildProjectReferenceExistent.Targets)" /> <_ProjectsForNativeManifest Include="@(_MSBuildProjectReferenceExistent)" - Condition="'%(_MSBuildProjectReferenceExistent.BuildReference)' == 'true' and '@(ProjectReferenceWithConfiguration)' != '' and '$(BuildingProject)' == 'true' and '@(_MSBuildProjectReferenceExistent)' != ''"> - %(_MSBuildProjectReferenceExistent.SetConfiguration); %(_MSBuildProjectReferenceExistent.SetPlatform); %(_MSBuildProjectReferenceExistent.SetTargetFramework) - %(_MSBuildProjectReferenceExistent.GlobalPropertiesToRemove)$(_GlobalPropertiesToRemoveFromProjectReferences) - + Condition="'%(_MSBuildProjectReferenceExistent.BuildReference)' == 'true' and '@(ProjectReferenceWithConfiguration)' != '' and '$(BuildingProject)' == 'true' and '@(_MSBuildProjectReferenceExistent)' != ''" + Properties="%(_MSBuildProjectReferenceExistent.SetConfiguration); %(_MSBuildProjectReferenceExistent.SetPlatform); %(_MSBuildProjectReferenceExistent.SetTargetFramework)" + RemoveProperties="%(_MSBuildProjectReferenceExistent.GlobalPropertiesToRemove)$(_GlobalPropertiesToRemoveFromProjectReferences)" /> Date: Mon, 5 Jan 2026 17:20:05 -0600 Subject: [PATCH 3/4] Reduce log verbosity in cases where effectively-empty values are passed in on Project TaskItems via metadata --- .../RequestBuilder/IntrinsicTasks/MSBuild.cs | 55 +++++++++------- src/Tasks/MSBuild.cs | 64 ++++++++++++------- .../Microsoft.Common.CurrentVersion.targets | 12 ++-- 3 files changed, 80 insertions(+), 51 deletions(-) diff --git a/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/MSBuild.cs b/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/MSBuild.cs index 59193af2a5e..c8197292a57 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/MSBuild.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/MSBuild.cs @@ -257,11 +257,10 @@ public async Task ExecuteInternal() } // Parse out the properties to undefine, if any. - string[] undefinePropertiesArray = null; - if (!String.IsNullOrEmpty(RemoveProperties)) + string[] undefinePropertiesArray = ExpandItemList(RemoveProperties); + if (undefinePropertiesArray.Length > 0) { Log.LogMessageFromResources(MessageImportance.Low, "General.UndefineProperties"); - undefinePropertiesArray = RemoveProperties.Split(MSBuildConstants.SemicolonChar, StringSplitOptions.RemoveEmptyEntries); foreach (string property in undefinePropertiesArray) { Log.LogMessageFromText($" {property}", MessageImportance.Low); @@ -455,6 +454,19 @@ private async Task BuildProjectsInParallel(Dictionary prop return success; } + /// + /// Expand a single item list into an array of strings - intended for use on scalar values that represent lists of items (e.g. Properties lists on an ITaskItem metadata) + /// + private static string[] ExpandItemList(string itemList) + { + if (string.IsNullOrEmpty(itemList)) + { + return Array.Empty(); + } + + return itemList.Split(TargetAndPropertyListSeparators, StringSplitOptions.RemoveEmptyEntries); + } + /// /// Expand and re-construct arrays of all targets and properties /// @@ -469,7 +481,7 @@ private void ExpandAllTargetsAndProperties() foreach (string p in Properties) { // Split each property according to the separators - string[] expandedPropertyValues = p.Split(TargetAndPropertyListSeparators, StringSplitOptions.RemoveEmptyEntries); + string[] expandedPropertyValues = ExpandItemList(p); // Add the resultant properties to the final list foreach (string property in expandedPropertyValues) { @@ -486,7 +498,7 @@ private void ExpandAllTargetsAndProperties() foreach (string t in Targets) { // Split each target according to the separators - string[] expandedTargetValues = t.Split(TargetAndPropertyListSeparators, StringSplitOptions.RemoveEmptyEntries); + string[] expandedTargetValues = ExpandItemList(t); // Add the resultant targets to the final list foreach (string target in expandedTargetValues) { @@ -563,15 +575,16 @@ internal static async Task ExecuteTargets( projectNames[i] = projects[i].ItemSpec; toolsVersions[i] = toolsVersion; + string[] projectSpecificPropertiesMetadata = ExpandItemList(projects[i].GetMetadata(ItemMetadataNames.PropertiesMetadataName)); // If the user specified a different set of global properties for this project, then // parse the string containing the properties - if (!String.IsNullOrEmpty(projects[i].GetMetadata(ItemMetadataNames.PropertiesMetadataName))) + if (projectSpecificPropertiesMetadata.Length > 0) { if (!PropertyParser.GetTableWithEscaping( log, ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("General.OverridingProperties", projectNames[i]), ItemMetadataNames.PropertiesMetadataName, - projects[i].GetMetadata(ItemMetadataNames.PropertiesMetadataName).Split(MSBuildConstants.SemicolonChar, StringSplitOptions.RemoveEmptyEntries), + projectSpecificPropertiesMetadata, out Dictionary preProjectPropertiesTable)) { return false; @@ -582,46 +595,44 @@ internal static async Task ExecuteTargets( if (undefineProperties != null) { - undefinePropertiesPerProject[i] = new List(undefineProperties); + undefinePropertiesPerProject[i] = [.. undefineProperties]; } // If the user wanted to undefine specific global properties for this project, parse // that string and remove them now. - string projectUndefineProperties = projects[i].GetMetadata(ItemMetadataNames.UndefinePropertiesMetadataName); - if (!String.IsNullOrEmpty(projectUndefineProperties)) + string[] projectSpecificUndefinePropertiesMetadata = ExpandItemList(projects[i].GetMetadata(ItemMetadataNames.UndefinePropertiesMetadataName)); + if (projectSpecificUndefinePropertiesMetadata.Length > 0) { - string[] propertiesToUndefine = projectUndefineProperties.Split(MSBuildConstants.SemicolonChar, StringSplitOptions.RemoveEmptyEntries); - if (undefinePropertiesPerProject[i] == null) - { - undefinePropertiesPerProject[i] = new List(propertiesToUndefine.Length); - } + undefinePropertiesPerProject[i] ??= projectSpecificUndefinePropertiesMetadata; - if (log != null && propertiesToUndefine.Length > 0) + if (log != null) { log.LogMessageFromResources(MessageImportance.Low, "General.ProjectUndefineProperties", projectNames[i]); - foreach (string property in propertiesToUndefine) + foreach (string property in projectSpecificUndefinePropertiesMetadata) { - undefinePropertiesPerProject[i].Add(property); log.LogMessageFromText($" {property}", MessageImportance.Low); } } } + string[] projectSpecificAdditionalPropertiesMetadata = ExpandItemList(projects[i].GetMetadata(ItemMetadataNames.AdditionalPropertiesMetadataName)); // If the user specified a different set of global properties for this project, then // parse the string containing the properties - if (!String.IsNullOrEmpty(projects[i].GetMetadata(ItemMetadataNames.AdditionalPropertiesMetadataName))) + if (projectSpecificAdditionalPropertiesMetadata.Length > 0) { if (!PropertyParser.GetTableWithEscaping( log, ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("General.AdditionalProperties", projectNames[i]), ItemMetadataNames.AdditionalPropertiesMetadataName, - projects[i].GetMetadata(ItemMetadataNames.AdditionalPropertiesMetadataName).Split(MSBuildConstants.SemicolonChar, StringSplitOptions.RemoveEmptyEntries), + projectSpecificAdditionalPropertiesMetadata, out Dictionary additionalProjectPropertiesTable)) { return false; } - var combinedTable = new Dictionary(StringComparer.OrdinalIgnoreCase); + // attempt to pre-size dictionaries - worst case we need to hold all entries from both, so assume that since huge amounts of properties are uncommon. + var combinedTable = new Dictionary(projectProperties[i]?.Count ?? 0 + additionalProjectPropertiesTable.Count, StringComparer.OrdinalIgnoreCase); + // todo(chusk): would it be faster to just clone the projectProperties[i] dictionary as a base, and add additionalProjectPropertiesTable on top of it? // First copy in the properties from the global table that not in the additional properties table if (projectProperties[i] != null) { @@ -642,7 +653,7 @@ internal static async Task ExecuteTargets( } // If the user specified a different toolsVersion for this project - then override the setting - if (!String.IsNullOrEmpty(projects[i].GetMetadata("ToolsVersion"))) + if (!string.IsNullOrEmpty(projects[i].GetMetadata("ToolsVersion"))) { toolsVersions[i] = projects[i].GetMetadata("ToolsVersion"); } diff --git a/src/Tasks/MSBuild.cs b/src/Tasks/MSBuild.cs index 2b505528928..6afbcbec270 100644 --- a/src/Tasks/MSBuild.cs +++ b/src/Tasks/MSBuild.cs @@ -214,11 +214,10 @@ public override bool Execute() } // Parse out the properties to undefine, if any. - string[] undefinePropertiesArray = null; - if (!String.IsNullOrEmpty(RemoveProperties)) + string[] undefinePropertiesArray = ExpandItemList(RemoveProperties); + if (undefinePropertiesArray.Length > 0) { Log.LogMessageFromResources(MessageImportance.Low, "General.UndefineProperties"); - undefinePropertiesArray = RemoveProperties.Split(MSBuildConstants.SemicolonChar); foreach (string property in undefinePropertiesArray) { Log.LogMessageFromText($" {property}", MessageImportance.Low); @@ -403,6 +402,19 @@ private bool BuildProjectsInParallel(Dictionary propertiesTable, return success; } + /// + /// Expand a single item list into an array of strings - intended for use on scalar values that represent lists of items (e.g. Properties lists on an ITaskItem metadata) + /// + private static string[] ExpandItemList(string itemList) + { + if (string.IsNullOrEmpty(itemList)) + { + return Array.Empty(); + } + + return itemList.Split(TargetAndPropertyListSeparators, StringSplitOptions.RemoveEmptyEntries); + } + /// /// Expand and re-construct arrays of all targets and properties /// @@ -417,7 +429,7 @@ private void ExpandAllTargetsAndProperties() foreach (string t in Properties) { // Split each property according to the separators - string[] expandedPropertyValues = t.Split(TargetAndPropertyListSeparators, StringSplitOptions.RemoveEmptyEntries); + string[] expandedPropertyValues = ExpandItemList(t); // Add the resultant properties to the final list foreach (string property in expandedPropertyValues) @@ -435,7 +447,7 @@ private void ExpandAllTargetsAndProperties() foreach (string t in Targets) { // Split each target according to the separators - string[] expandedTargetValues = t.Split(TargetAndPropertyListSeparators, StringSplitOptions.RemoveEmptyEntries); + string[] expandedTargetValues = ExpandItemList(t); // Add the resultant targets to the final list foreach (string target in expandedTargetValues) @@ -513,11 +525,15 @@ internal static bool ExecuteTargets( // If the user specified a different set of global properties for this project, then // parse the string containing the properties - if (!String.IsNullOrEmpty(projects[i].GetMetadata("Properties"))) + string[] projectSpecificPropertiesMetadata = ExpandItemList(projects[i].GetMetadata("Properties")); + if (projectSpecificPropertiesMetadata.Length > 0) { if (!PropertyParser.GetTableWithEscaping( - log, ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("General.OverridingProperties", projectNames[i]), "Properties", projects[i].GetMetadata("Properties").Split(MSBuildConstants.SemicolonChar), - out Dictionary preProjectPropertiesTable)) + log, + ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("General.OverridingProperties", projectNames[i]), + "Properties", + projectSpecificPropertiesMetadata, + out Dictionary preProjectPropertiesTable)) { return false; } @@ -526,26 +542,21 @@ internal static bool ExecuteTargets( if (undefineProperties != null) { - undefinePropertiesPerProject[i] = new List(undefineProperties); + undefinePropertiesPerProject[i] = [.. undefineProperties]; } // If the user wanted to undefine specific global properties for this project, parse // that string and remove them now. - string projectUndefineProperties = projects[i].GetMetadata("UndefineProperties"); - if (!String.IsNullOrEmpty(projectUndefineProperties)) + string[] projectSpecificUndefinePropertiesMetadata = ExpandItemList(projects[i].GetMetadata("UndefineProperties")); + if (projectSpecificUndefinePropertiesMetadata.Length > 0) { - string[] propertiesToUndefine = projectUndefineProperties.Split(MSBuildConstants.SemicolonChar); - if (undefinePropertiesPerProject[i] == null) - { - undefinePropertiesPerProject[i] = new List(propertiesToUndefine.Length); - } + undefinePropertiesPerProject[i] ??= projectSpecificUndefinePropertiesMetadata; - if (log != null && propertiesToUndefine.Length > 0) + if (log != null) { log.LogMessageFromResources(MessageImportance.Low, "General.ProjectUndefineProperties", projectNames[i]); - foreach (string property in propertiesToUndefine) + foreach (string property in projectSpecificUndefinePropertiesMetadata) { - undefinePropertiesPerProject[i].Add(property); log.LogMessageFromText($" {property}", MessageImportance.Low); } } @@ -553,15 +564,22 @@ internal static bool ExecuteTargets( // If the user specified a different set of global properties for this project, then // parse the string containing the properties - if (!String.IsNullOrEmpty(projects[i].GetMetadata("AdditionalProperties"))) + string[] projectSpecificAdditionalPropertiesMetadata = ExpandItemList(projects[i].GetMetadata("AdditionalProperties")); + if (projectSpecificAdditionalPropertiesMetadata.Length > 0) { if (!PropertyParser.GetTableWithEscaping( - log, ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("General.AdditionalProperties", projectNames[i]), "AdditionalProperties", projects[i].GetMetadata("AdditionalProperties").Split(MSBuildConstants.SemicolonChar), - out Dictionary additionalProjectPropertiesTable)) + log, + ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("General.AdditionalProperties", projectNames[i]), + "AdditionalProperties", + projectSpecificAdditionalPropertiesMetadata, + out Dictionary additionalProjectPropertiesTable)) { return false; } - var combinedTable = new Dictionary(StringComparer.OrdinalIgnoreCase); + + // attempt to pre-size dictionaries - worst case we need to hold all entries from both, so assume that since huge amounts of properties are uncommon. + var combinedTable = new Dictionary(projectProperties[i]?.Count ?? 0 + additionalProjectPropertiesTable.Count, StringComparer.OrdinalIgnoreCase); + // todo(chusk): would it be faster to just clone the projectProperties[i] dictionary as a base, and add additionalProjectPropertiesTable on top of it? // First copy in the properties from the global table that not in the additional properties table if (projectProperties[i] != null) { diff --git a/src/Tasks/Microsoft.Common.CurrentVersion.targets b/src/Tasks/Microsoft.Common.CurrentVersion.targets index 7152eb3065f..86151717f6b 100644 --- a/src/Tasks/Microsoft.Common.CurrentVersion.targets +++ b/src/Tasks/Microsoft.Common.CurrentVersion.targets @@ -2131,8 +2131,8 @@ Copyright (C) Microsoft Corporation. All rights reserved. <_ProjectsForGetTargetPath Include="@(_MSBuildProjectReferenceExistent)" Condition="'%(_MSBuildProjectReferenceExistent.BuildReference)' == 'true' and '@(ProjectReferenceWithConfiguration)' != '' and ('$(BuildingInsideVisualStudio)' == 'true' or '$(BuildProjectReferences)' != 'true') and '$(VisualStudioVersion)' != '10.0' and '@(_MSBuildProjectReferenceExistent)' != ''" - Properties="%(_MSBuildProjectReferenceExistent.SetConfiguration); %(_MSBuildProjectReferenceExistent.SetPlatform); %(_MSBuildProjectReferenceExistent.SetTargetFramework)" - RemoveProperties="%(_MSBuildProjectReferenceExistent.GlobalPropertiesToRemove)$(_GlobalPropertiesToRemoveFromProjectReferences)" /> + Properties="%(_MSBuildProjectReferenceExistent.SetConfiguration);%(_MSBuildProjectReferenceExistent.SetPlatform);%(_MSBuildProjectReferenceExistent.SetTargetFramework)" + RemoveProperties="%(_MSBuildProjectReferenceExistent.GlobalPropertiesToRemove);$(_GlobalPropertiesToRemoveFromProjectReferences)" /> <_ProjectsForCommandLineBuild Include="@(_MSBuildProjectReferenceExistent)" Condition="'%(_MSBuildProjectReferenceExistent.BuildReference)' == 'true' and '@(ProjectReferenceWithConfiguration)' != '' and '$(BuildingInsideVisualStudio)' != 'true' and '$(BuildProjectReferences)' == 'true' and '@(_MSBuildProjectReferenceExistent)' != ''" - Properties="%(_MSBuildProjectReferenceExistent.SetConfiguration); %(_MSBuildProjectReferenceExistent.SetPlatform); %(_MSBuildProjectReferenceExistent.SetTargetFramework)" - RemoveProperties="%(_MSBuildProjectReferenceExistent.GlobalPropertiesToRemove)$(_GlobalPropertiesToRemoveFromProjectReferences)" + Properties="%(_MSBuildProjectReferenceExistent.SetConfiguration);%(_MSBuildProjectReferenceExistent.SetPlatform);%(_MSBuildProjectReferenceExistent.SetTargetFramework)" + RemoveProperties="%(_MSBuildProjectReferenceExistent.GlobalPropertiesToRemove);$(_GlobalPropertiesToRemoveFromProjectReferences)" Targets="%(_MSBuildProjectReferenceExistent.Targets)" /> @@ -2179,8 +2179,8 @@ Copyright (C) Microsoft Corporation. All rights reserved. <_ProjectsForNativeManifest Include="@(_MSBuildProjectReferenceExistent)" Condition="'%(_MSBuildProjectReferenceExistent.BuildReference)' == 'true' and '@(ProjectReferenceWithConfiguration)' != '' and '$(BuildingProject)' == 'true' and '@(_MSBuildProjectReferenceExistent)' != ''" - Properties="%(_MSBuildProjectReferenceExistent.SetConfiguration); %(_MSBuildProjectReferenceExistent.SetPlatform); %(_MSBuildProjectReferenceExistent.SetTargetFramework)" - RemoveProperties="%(_MSBuildProjectReferenceExistent.GlobalPropertiesToRemove)$(_GlobalPropertiesToRemoveFromProjectReferences)" /> + Properties="%(_MSBuildProjectReferenceExistent.SetConfiguration);%(_MSBuildProjectReferenceExistent.SetPlatform);%(_MSBuildProjectReferenceExistent.SetTargetFramework)" + RemoveProperties="%(_MSBuildProjectReferenceExistent.GlobalPropertiesToRemove);$(_GlobalPropertiesToRemoveFromProjectReferences)" /> Date: Mon, 5 Jan 2026 17:41:26 -0600 Subject: [PATCH 4/4] Fix some silly mistakes that code review caught. --- .../IntrinsicTasks/CallTarget.cs | 3 +- .../RequestBuilder/IntrinsicTasks/MSBuild.cs | 38 ++++++++++++------ src/Tasks/CallTarget.cs | 23 ++++++----- src/Tasks/MSBuild.cs | 40 ++++++++++++------- 4 files changed, 65 insertions(+), 39 deletions(-) diff --git a/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/CallTarget.cs b/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/CallTarget.cs index ef8c7ff39a3..681101088c1 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/CallTarget.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/CallTarget.cs @@ -114,7 +114,8 @@ public Task ExecuteInternal() targetOutputs: _targetOutputs, unloadProjectsOnCompletion: false, toolsVersion: null, - skipNonexistentTargets: false); + skipNonexistentTargets: false, + targetAndPropertyListSeparators: null); } #endregion diff --git a/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/MSBuild.cs b/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/MSBuild.cs index c8197292a57..785b0af2fac 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/MSBuild.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/MSBuild.cs @@ -206,6 +206,8 @@ public string SkipNonexistentProjects /// public string[] TargetAndPropertyListSeparators { get; set; } = null; + private static string[] SemicolonSeparatorArray = [";"]; + /// /// If set, MSBuild will skip the targets specified in this build request if they are not defined in the /// to build. This only applies to this build request (if another target calls the @@ -257,7 +259,7 @@ public async Task ExecuteInternal() } // Parse out the properties to undefine, if any. - string[] undefinePropertiesArray = ExpandItemList(RemoveProperties); + string[] undefinePropertiesArray = ExpandItemList(TargetAndPropertyListSeparators ?? SemicolonSeparatorArray, RemoveProperties); if (undefinePropertiesArray.Length > 0) { Log.LogMessageFromResources(MessageImportance.Low, "General.UndefineProperties"); @@ -373,7 +375,8 @@ public async Task ExecuteInternal() _targetOutputs, UnloadProjectsOnCompletion, ToolsVersion, - SkipNonexistentTargets); + SkipNonexistentTargets, + TargetAndPropertyListSeparators); if (!executeResult) { @@ -444,7 +447,8 @@ private async Task BuildProjectsInParallel(Dictionary prop _targetOutputs, UnloadProjectsOnCompletion, ToolsVersion, - SkipNonexistentTargets); + SkipNonexistentTargets, + TargetAndPropertyListSeparators); if (!executeResult) { @@ -457,14 +461,14 @@ private async Task BuildProjectsInParallel(Dictionary prop /// /// Expand a single item list into an array of strings - intended for use on scalar values that represent lists of items (e.g. Properties lists on an ITaskItem metadata) /// - private static string[] ExpandItemList(string itemList) + private static string[] ExpandItemList(string[] separators, string itemList) { if (string.IsNullOrEmpty(itemList)) { return Array.Empty(); } - return itemList.Split(TargetAndPropertyListSeparators, StringSplitOptions.RemoveEmptyEntries); + return itemList.Split(separators, StringSplitOptions.RemoveEmptyEntries); } /// @@ -481,7 +485,7 @@ private void ExpandAllTargetsAndProperties() foreach (string p in Properties) { // Split each property according to the separators - string[] expandedPropertyValues = ExpandItemList(p); + string[] expandedPropertyValues = ExpandItemList(TargetAndPropertyListSeparators ?? SemicolonSeparatorArray, p); // Add the resultant properties to the final list foreach (string property in expandedPropertyValues) { @@ -498,7 +502,7 @@ private void ExpandAllTargetsAndProperties() foreach (string t in Targets) { // Split each target according to the separators - string[] expandedTargetValues = ExpandItemList(t); + string[] expandedTargetValues = ExpandItemList(TargetAndPropertyListSeparators ?? SemicolonSeparatorArray, t); // Add the resultant targets to the final list foreach (string target in expandedTargetValues) { @@ -549,7 +553,8 @@ internal static async Task ExecuteTargets( List targetOutputs, bool unloadProjectsOnCompletion, string toolsVersion, - bool skipNonexistentTargets) + bool skipNonexistentTargets, + string[] targetAndPropertyListSeparators) { bool success = true; @@ -575,7 +580,7 @@ internal static async Task ExecuteTargets( projectNames[i] = projects[i].ItemSpec; toolsVersions[i] = toolsVersion; - string[] projectSpecificPropertiesMetadata = ExpandItemList(projects[i].GetMetadata(ItemMetadataNames.PropertiesMetadataName)); + string[] projectSpecificPropertiesMetadata = ExpandItemList(targetAndPropertyListSeparators ?? SemicolonSeparatorArray, projects[i].GetMetadata(ItemMetadataNames.PropertiesMetadataName)); // If the user specified a different set of global properties for this project, then // parse the string containing the properties if (projectSpecificPropertiesMetadata.Length > 0) @@ -600,10 +605,17 @@ internal static async Task ExecuteTargets( // If the user wanted to undefine specific global properties for this project, parse // that string and remove them now. - string[] projectSpecificUndefinePropertiesMetadata = ExpandItemList(projects[i].GetMetadata(ItemMetadataNames.UndefinePropertiesMetadataName)); + string[] projectSpecificUndefinePropertiesMetadata = ExpandItemList(targetAndPropertyListSeparators ?? SemicolonSeparatorArray, projects[i].GetMetadata(ItemMetadataNames.UndefinePropertiesMetadataName)); if (projectSpecificUndefinePropertiesMetadata.Length > 0) { - undefinePropertiesPerProject[i] ??= projectSpecificUndefinePropertiesMetadata; + if (undefinePropertiesPerProject[i] is null) + { + undefinePropertiesPerProject[i] = [.. projectSpecificUndefinePropertiesMetadata]; + } + else + { + undefinePropertiesPerProject[i].AddRange(projectSpecificUndefinePropertiesMetadata); + } if (log != null) { @@ -615,7 +627,7 @@ internal static async Task ExecuteTargets( } } - string[] projectSpecificAdditionalPropertiesMetadata = ExpandItemList(projects[i].GetMetadata(ItemMetadataNames.AdditionalPropertiesMetadataName)); + string[] projectSpecificAdditionalPropertiesMetadata = ExpandItemList(targetAndPropertyListSeparators ?? SemicolonSeparatorArray, projects[i].GetMetadata(ItemMetadataNames.AdditionalPropertiesMetadataName)); // If the user specified a different set of global properties for this project, then // parse the string containing the properties if (projectSpecificAdditionalPropertiesMetadata.Length > 0) @@ -631,7 +643,7 @@ internal static async Task ExecuteTargets( } // attempt to pre-size dictionaries - worst case we need to hold all entries from both, so assume that since huge amounts of properties are uncommon. - var combinedTable = new Dictionary(projectProperties[i]?.Count ?? 0 + additionalProjectPropertiesTable.Count, StringComparer.OrdinalIgnoreCase); + var combinedTable = new Dictionary((projectProperties[i]?.Count ?? 0) + additionalProjectPropertiesTable.Count, StringComparer.OrdinalIgnoreCase); // todo(chusk): would it be faster to just clone the projectProperties[i] dictionary as a base, and add additionalProjectPropertiesTable on top of it? // First copy in the properties from the global table that not in the additional properties table if (projectProperties[i] != null) diff --git a/src/Tasks/CallTarget.cs b/src/Tasks/CallTarget.cs index 74acb20e7c3..bc60602a4e1 100644 --- a/src/Tasks/CallTarget.cs +++ b/src/Tasks/CallTarget.cs @@ -77,17 +77,18 @@ public override bool Execute() // Build the specified targets in the current project. return MSBuild.ExecuteTargets( - singleProject, // project = null (current project) - null, // propertiesTable = null - null, // undefineProperties - targetLists, // list of targets to build - false, // stopOnFirstFailure = false - false, // rebaseOutputs = false - BuildEngine3, - Log, - _targetOutputs, - false, - null); // toolsVersion = null + projects: singleProject, + propertiesTable: null, + undefineProperties: null, + targetLists: targetLists, + stopOnFirstFailure: false, + rebaseOutputs: false, + buildEngine: BuildEngine3, + log: Log, + targetOutputs: _targetOutputs, + unloadProjectsOnCompletion: false, + toolsVersion: null, + targetAndPropertyListSeparators: null); } #endregion diff --git a/src/Tasks/MSBuild.cs b/src/Tasks/MSBuild.cs index 6afbcbec270..51219e3f8f4 100644 --- a/src/Tasks/MSBuild.cs +++ b/src/Tasks/MSBuild.cs @@ -184,6 +184,8 @@ public string SkipNonexistentProjects /// public string[] TargetAndPropertyListSeparators { get; set; } + private static string[] SemicolonSeparatorArray = [";"]; + #endregion #region ITask Members @@ -214,7 +216,7 @@ public override bool Execute() } // Parse out the properties to undefine, if any. - string[] undefinePropertiesArray = ExpandItemList(RemoveProperties); + string[] undefinePropertiesArray = ExpandItemList(TargetAndPropertyListSeparators ?? SemicolonSeparatorArray, RemoveProperties); if (undefinePropertiesArray.Length > 0) { Log.LogMessageFromResources(MessageImportance.Low, "General.UndefineProperties"); @@ -330,7 +332,8 @@ public override bool Execute() Log, _targetOutputs, UnloadProjectsOnCompletion, - ToolsVersion)) + ToolsVersion, + TargetAndPropertyListSeparators)) { success = false; } @@ -394,7 +397,8 @@ private bool BuildProjectsInParallel(Dictionary propertiesTable, Log, _targetOutputs, UnloadProjectsOnCompletion, - ToolsVersion)) + ToolsVersion, + TargetAndPropertyListSeparators)) { success = false; } @@ -405,14 +409,14 @@ private bool BuildProjectsInParallel(Dictionary propertiesTable, /// /// Expand a single item list into an array of strings - intended for use on scalar values that represent lists of items (e.g. Properties lists on an ITaskItem metadata) /// - private static string[] ExpandItemList(string itemList) + private static string[] ExpandItemList(string[] separators, string itemList) { if (string.IsNullOrEmpty(itemList)) { return Array.Empty(); } - return itemList.Split(TargetAndPropertyListSeparators, StringSplitOptions.RemoveEmptyEntries); + return itemList.Split(separators, StringSplitOptions.RemoveEmptyEntries); } /// @@ -429,7 +433,7 @@ private void ExpandAllTargetsAndProperties() foreach (string t in Properties) { // Split each property according to the separators - string[] expandedPropertyValues = ExpandItemList(t); + string[] expandedPropertyValues = ExpandItemList(TargetAndPropertyListSeparators ?? SemicolonSeparatorArray, t); // Add the resultant properties to the final list foreach (string property in expandedPropertyValues) @@ -447,7 +451,7 @@ private void ExpandAllTargetsAndProperties() foreach (string t in Targets) { // Split each target according to the separators - string[] expandedTargetValues = ExpandItemList(t); + string[] expandedTargetValues = ExpandItemList(TargetAndPropertyListSeparators ?? SemicolonSeparatorArray, t); // Add the resultant targets to the final list foreach (string target in expandedTargetValues) @@ -497,7 +501,8 @@ internal static bool ExecuteTargets( TaskLoggingHelper log, List targetOutputs, bool unloadProjectsOnCompletion, - string toolsVersion) + string toolsVersion, + string[] targetAndPropertyListSeparators) { bool success = true; @@ -508,7 +513,7 @@ internal static bool ExecuteTargets( var projectNames = new string[projects.Count]; var toolsVersions = new string[projects.Count]; var projectProperties = new Dictionary[projects.Count]; - var undefinePropertiesPerProject = new IList[projects.Count]; + var undefinePropertiesPerProject = new List[projects.Count]; for (int i = 0; i < projectNames.Length; i++) { @@ -525,7 +530,7 @@ internal static bool ExecuteTargets( // If the user specified a different set of global properties for this project, then // parse the string containing the properties - string[] projectSpecificPropertiesMetadata = ExpandItemList(projects[i].GetMetadata("Properties")); + string[] projectSpecificPropertiesMetadata = ExpandItemList(targetAndPropertyListSeparators ?? SemicolonSeparatorArray, projects[i].GetMetadata("Properties")); if (projectSpecificPropertiesMetadata.Length > 0) { if (!PropertyParser.GetTableWithEscaping( @@ -547,10 +552,17 @@ internal static bool ExecuteTargets( // If the user wanted to undefine specific global properties for this project, parse // that string and remove them now. - string[] projectSpecificUndefinePropertiesMetadata = ExpandItemList(projects[i].GetMetadata("UndefineProperties")); + string[] projectSpecificUndefinePropertiesMetadata = ExpandItemList(targetAndPropertyListSeparators ?? SemicolonSeparatorArray, projects[i].GetMetadata("UndefineProperties")); if (projectSpecificUndefinePropertiesMetadata.Length > 0) { - undefinePropertiesPerProject[i] ??= projectSpecificUndefinePropertiesMetadata; + if (undefinePropertiesPerProject[i] == null) + { + undefinePropertiesPerProject[i] = [.. projectSpecificUndefinePropertiesMetadata]; + } + else + { + undefinePropertiesPerProject[i].AddRange(projectSpecificUndefinePropertiesMetadata); + } if (log != null) { @@ -564,7 +576,7 @@ internal static bool ExecuteTargets( // If the user specified a different set of global properties for this project, then // parse the string containing the properties - string[] projectSpecificAdditionalPropertiesMetadata = ExpandItemList(projects[i].GetMetadata("AdditionalProperties")); + string[] projectSpecificAdditionalPropertiesMetadata = ExpandItemList(targetAndPropertyListSeparators ?? SemicolonSeparatorArray, projects[i].GetMetadata("AdditionalProperties")); if (projectSpecificAdditionalPropertiesMetadata.Length > 0) { if (!PropertyParser.GetTableWithEscaping( @@ -578,7 +590,7 @@ internal static bool ExecuteTargets( } // attempt to pre-size dictionaries - worst case we need to hold all entries from both, so assume that since huge amounts of properties are uncommon. - var combinedTable = new Dictionary(projectProperties[i]?.Count ?? 0 + additionalProjectPropertiesTable.Count, StringComparer.OrdinalIgnoreCase); + var combinedTable = new Dictionary((projectProperties[i]?.Count ?? 0) + additionalProjectPropertiesTable.Count, StringComparer.OrdinalIgnoreCase); // todo(chusk): would it be faster to just clone the projectProperties[i] dictionary as a base, and add additionalProjectPropertiesTable on top of it? // First copy in the properties from the global table that not in the additional properties table if (projectProperties[i] != null)