Skip to content
Merged
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 @@ -413,27 +413,28 @@ private List<ProjectItemInstance> ExpandItemIntoItems(

// Split Include on any semicolons, and take each split in turn
var includeSplits = ExpressionShredder.SplitSemiColonSeparatedList(evaluatedInclude);
ProjectItemInstanceFactory itemFactory = new ProjectItemInstanceFactory(this.Project, originalItem.ItemType);
ProjectItemInstanceFactory itemFactory = new ProjectItemInstanceFactory(Project, originalItem.ItemType);

// EngineFileUtilities.GetFileListEscaped api invocation evaluates excludes by default.
// If the code process any expression like "@(x)", we need to handle excludes explicitly using EvaluateExcludePaths().
bool anyTransformExprProceeded = false;

foreach (string includeSplit in includeSplits)
{
// If expression is "@(x)" copy specified list with its metadata, otherwise just treat as string
bool throwaway;

IList<ProjectItemInstance> itemsFromSplit = expander.ExpandSingleItemVectorExpressionIntoItems(includeSplit,
IList<ProjectItemInstance> itemsFromSplit = expander.ExpandSingleItemVectorExpressionIntoItems(
includeSplit,
itemFactory,
ExpanderOptions.ExpandItems,
false /* do not include null expansion results */,
out throwaway,
out _,
originalItem.IncludeLocation);

if (itemsFromSplit != null)
{
// Expression is in form "@(X)", so add these items directly.
foreach (ProjectItemInstance item in itemsFromSplit)
{
items.Add(item);
}
items.AddRange(itemsFromSplit);
anyTransformExprProceeded = true;
}
else
{
Expand Down Expand Up @@ -463,35 +464,18 @@ private List<ProjectItemInstance> ExpandItemIntoItems(
}
}

// Evaluate, split, expand and subtract any Exclude
HashSet<string> excludesUnescapedForComparison = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

foreach (string excludeSplit in excludes)
// There is a need to Evaluate Exclude part explicitly because of of the expressions had the form "@(X)".
if (anyTransformExprProceeded)
{
string[] excludeSplitFiles = EngineFileUtilities.GetFileListUnescaped(
Project.Directory,
excludeSplit,
loggingMechanism: LoggingContext,
excludeLocation: originalItem.ExcludeLocation);
// Calculate all Exclude
var excludesUnescapedForComparison = EvaluateExcludePaths(excludes, originalItem.ExcludeLocation);

foreach (string excludeSplitFile in excludeSplitFiles)
{
excludesUnescapedForComparison.Add(excludeSplitFile.NormalizeForPathComparison());
}
}

List<ProjectItemInstance> remainingItems = new List<ProjectItemInstance>();

for (int i = 0; i < items.Count; i++)
{
if (!excludesUnescapedForComparison.Contains(((IItem)items[i]).EvaluatedInclude.NormalizeForPathComparison()))
{
remainingItems.Add(items[i]);
}
// Subtract any Exclude
items = items
.Where(i => !excludesUnescapedForComparison.Contains(((IItem)i).EvaluatedInclude.NormalizeForPathComparison()))
.ToList();
}

items = remainingItems;

// Filter the metadata as appropriate
if (keepMetadata != null)
{
Expand Down Expand Up @@ -519,6 +503,32 @@ private List<ProjectItemInstance> ExpandItemIntoItems(
return items;
}

/// <summary>
/// Returns a list of all items specified in Exclude parameter.
/// If no items match, returns empty list.
/// </summary>
/// <param name="excludes">The items to match</param>
/// <param name="excludeLocation">The specification to match against the items.</param>
/// <returns>A list of matching items</returns>
private HashSet<string> EvaluateExcludePaths(IReadOnlyList<string> excludes, ElementLocation excludeLocation)
{
HashSet<string> excludesUnescapedForComparison = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (string excludeSplit in excludes)
{
string[] excludeSplitFiles = EngineFileUtilities.GetFileListUnescaped(
Project.Directory,
excludeSplit,
loggingMechanism: LoggingContext,
excludeLocation: excludeLocation);
foreach (string excludeSplitFile in excludeSplitFiles)
{
excludesUnescapedForComparison.Add(excludeSplitFile.NormalizeForPathComparison());
}
}

return excludesUnescapedForComparison;
}

/// <summary>
/// Returns a list of all items in the provided item group whose itemspecs match the specification, after it is split and any wildcards are expanded.
/// If no items match, returns null.
Expand Down
6 changes: 4 additions & 2 deletions src/Build/Utilities/EngineFileUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,10 @@ private static string[] GetFileList(
FileMatcher.SearchAction action = FileMatcher.SearchAction.None;
string excludeFileSpec = string.Empty;

if (!FilespecHasWildcards(filespecEscaped) ||
FilespecMatchesLazyWildcard(filespecEscaped, forceEvaluateWildCards))
var noWildcards = !FilespecHasWildcards(filespecEscaped) || FilespecMatchesLazyWildcard(filespecEscaped, forceEvaluateWildCards);

// It is possible to return original string if no wildcard matches and no entries in Exclude set.
if (noWildcards && excludeSpecsEscaped?.Any() != true)
{
// Just return the original string.
fileList = new string[] { returnEscaped ? filespecEscaped : EscapingUtilities.UnescapeAll(filespecEscaped) };
Expand Down