Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ public Task<bool> ExecuteInternal()
targetOutputs: _targetOutputs,
unloadProjectsOnCompletion: false,
toolsVersion: null,
skipNonexistentTargets: false);
skipNonexistentTargets: false,
targetAndPropertyListSeparators: null);
}

#endregion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,8 @@ public string SkipNonexistentProjects
/// </summary>
public string[] TargetAndPropertyListSeparators { get; set; } = null;

private static string[] SemicolonSeparatorArray = [";"];

/// <summary>
/// If set, MSBuild will skip the targets specified in this build request if they are not defined in the
/// <see cref="Projects"/> to build. This only applies to this build request (if another target calls the
Expand Down Expand Up @@ -257,11 +259,10 @@ public async Task<bool> ExecuteInternal()
}

// Parse out the properties to undefine, if any.
string[] undefinePropertiesArray = null;
if (!String.IsNullOrEmpty(RemoveProperties))
string[] undefinePropertiesArray = ExpandItemList(TargetAndPropertyListSeparators ?? SemicolonSeparatorArray, 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);
Expand Down Expand Up @@ -374,7 +375,8 @@ public async Task<bool> ExecuteInternal()
_targetOutputs,
UnloadProjectsOnCompletion,
ToolsVersion,
SkipNonexistentTargets);
SkipNonexistentTargets,
TargetAndPropertyListSeparators);

if (!executeResult)
{
Expand Down Expand Up @@ -445,7 +447,8 @@ private async Task<bool> BuildProjectsInParallel(Dictionary<string, string> prop
_targetOutputs,
UnloadProjectsOnCompletion,
ToolsVersion,
SkipNonexistentTargets);
SkipNonexistentTargets,
TargetAndPropertyListSeparators);

if (!executeResult)
{
Expand All @@ -455,6 +458,19 @@ private async Task<bool> BuildProjectsInParallel(Dictionary<string, string> prop
return success;
}

/// <summary>
/// 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)
/// </summary>
private static string[] ExpandItemList(string[] separators, string itemList)
{
if (string.IsNullOrEmpty(itemList))
{
return Array.Empty<string>();
}

return itemList.Split(separators, StringSplitOptions.RemoveEmptyEntries);
}

/// <summary>
/// Expand and re-construct arrays of all targets and properties
/// </summary>
Expand All @@ -469,7 +485,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(TargetAndPropertyListSeparators ?? SemicolonSeparatorArray, p);
// Add the resultant properties to the final list
foreach (string property in expandedPropertyValues)
{
Expand All @@ -486,7 +502,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(TargetAndPropertyListSeparators ?? SemicolonSeparatorArray, t);
// Add the resultant targets to the final list
foreach (string target in expandedTargetValues)
{
Expand Down Expand Up @@ -537,7 +553,8 @@ internal static async Task<bool> ExecuteTargets(
List<ITaskItem> targetOutputs,
bool unloadProjectsOnCompletion,
string toolsVersion,
bool skipNonexistentTargets)
bool skipNonexistentTargets,
string[] targetAndPropertyListSeparators)
{
bool success = true;

Expand All @@ -563,15 +580,16 @@ internal static async Task<bool> ExecuteTargets(
projectNames[i] = projects[i].ItemSpec;
toolsVersions[i] = toolsVersion;

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 (!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<string, string> preProjectPropertiesTable))
{
return false;
Expand All @@ -582,46 +600,51 @@ internal static async Task<bool> ExecuteTargets(

if (undefineProperties != null)
{
undefinePropertiesPerProject[i] = new List<string>(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(targetAndPropertyListSeparators ?? SemicolonSeparatorArray, projects[i].GetMetadata(ItemMetadataNames.UndefinePropertiesMetadataName));
if (projectSpecificUndefinePropertiesMetadata.Length > 0)
{
string[] propertiesToUndefine = projectUndefineProperties.Split(MSBuildConstants.SemicolonChar, StringSplitOptions.RemoveEmptyEntries);
if (undefinePropertiesPerProject[i] == null)
if (undefinePropertiesPerProject[i] is null)
{
undefinePropertiesPerProject[i] = [.. projectSpecificUndefinePropertiesMetadata];
}
else
{
undefinePropertiesPerProject[i] = new List<string>(propertiesToUndefine.Length);
undefinePropertiesPerProject[i].AddRange(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(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 (!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<string, string> additionalProjectPropertiesTable))
{
return false;
}

var combinedTable = new Dictionary<string, string>(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<string, string>((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)
{
Expand All @@ -642,7 +665,7 @@ internal static async Task<bool> 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");
}
Expand Down
23 changes: 12 additions & 11 deletions src/Tasks/CallTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading