Skip to content

Commit

Permalink
Add WithoutMetadataValue (#8867)
Browse files Browse the repository at this point in the history
Fixes #8205

Context
The following set of item functions allow to condition on or filter items based on metadata:

AnyHaveMetdataValue
HasMetadata
WithMetadataValue
These functions make it super simple to include items or condition based on metadata during evaluation time, without the need of batching (inside a target).

Over the last years I often wished that there would also be a WithoutMetadataValue item function so that the following pattern wouldn't require an extra target:

<Target Name="GetCompileFilteredItems">
  <ItemGroup>
    <CompileFiltered Include="@(Compile->WithMetadataValue('ExcludeFromX', ''))" />
    <CompileFiltered Include="@(Compile->WithMetadataValue('ExcludeFromX', 'false'))" />
  </ItemGroup>
</Target>

<Target Name="X"
             Inputs="@(CompileFiltered)"
             Outputs="..."
             DependsOnTargets="GetCompileFilteredItems">
  ...
</Target>
Instead, with a WithoutMetadtaValue item function, the filtering can happen just in-time without a separate target.

<Target Name="X"
             Inputs="@(Compile->WithoutMetadataValue('ExcludeFromX', 'true'))"
             Outputs="...">
  ...
</Target>
Changes Made
Add one new function WithoutMetadataValue

Testing
Add one test WithoutMetadataValue()
  • Loading branch information
JaynieBai committed Jun 27, 2023
1 parent 1a97c09 commit a0468d8
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 18 deletions.
31 changes: 31 additions & 0 deletions src/Build.UnitTests/Evaluation/Expander_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1118,6 +1118,37 @@ public void HasMetadata()
logger.AssertLogContains("[One|Three|Four]");
}


/// <summary>
/// Filter items by WithoutMetadataValue function
/// </summary>
[Fact]
public void WithoutMetadataValue()
{
MockLogger logger = Helpers.BuildProjectWithNewOMExpectSuccess("""
<Project>
<ItemGroup>
<_Item Include="One">
<A>true</A>
</_Item>
<_Item Include="Two">
<A>false</A>
</_Item>
<_Item Include="Three">
<A></A>
</_Item>
<_Item Include="Four">
<B></B>
</_Item>
</ItemGroup>
<Target Name="AfterBuild">
<Message Text="[@(_Item->WithoutMetadataValue('a', 'true'),'|')]"/>
</Target>
</Project>
""");

logger.AssertLogContains("[Two|Three|Four]");
}
[Fact]
public void DirectItemMetadataReferenceShouldBeCaseInsensitive()
{
Expand Down
57 changes: 39 additions & 18 deletions src/Build/Evaluation/Expander.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2581,12 +2581,9 @@ internal static ItemTransformFunction GetItemTransformFunction(IElementLocation
{
metadataValue = item.Value.GetMetadataValueEscaped(metadataName);
}
catch (ArgumentException ex) // Blank metadata name
{
ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "CannotEvaluateItemMetadata", metadataName, ex.Message);
}
catch (InvalidOperationException ex)
catch (Exception ex) when (ex is ArgumentException || ex is InvalidOperationException)
{
// Blank metadata name
ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "CannotEvaluateItemMetadata", metadataName, ex.Message);
}

Expand Down Expand Up @@ -2786,12 +2783,9 @@ internal static ItemTransformFunction GetItemTransformFunction(IElementLocation
{
metadataValue = item.Value.GetMetadataValueEscaped(metadataName);
}
catch (ArgumentException ex) // Blank metadata name
{
ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "CannotEvaluateItemMetadata", metadataName, ex.Message);
}
catch (InvalidOperationException ex)
catch (Exception ex) when (ex is ArgumentException || ex is InvalidOperationException)
{
// Blank metadata name
ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "CannotEvaluateItemMetadata", metadataName, ex.Message);
}

Expand Down Expand Up @@ -2824,16 +2818,46 @@ internal static ItemTransformFunction GetItemTransformFunction(IElementLocation
{
metadataValue = item.Value.GetMetadataValueEscaped(metadataName);
}
catch (ArgumentException ex) // Blank metadata name
catch (Exception ex) when (ex is ArgumentException || ex is InvalidOperationException)
{
// Blank metadata name
ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "CannotEvaluateItemMetadata", metadataName, ex.Message);
}
catch (InvalidOperationException ex)

if (metadataValue != null && String.Equals(metadataValue, metadataValueToFind, StringComparison.OrdinalIgnoreCase))
{
// return a result through the enumerator
yield return new Pair<string, S>(item.Key, item.Value);
}
}
}

/// <summary>
/// Intrinsic function that returns those items don't have the given metadata value
/// Using a case insensitive comparison.
/// </summary>
internal static IEnumerable<Pair<string, S>> WithoutMetadataValue(Expander<P, I> expander, IElementLocation elementLocation, bool includeNullEntries, string functionName, IEnumerable<Pair<string, S>> itemsOfType, string[] arguments)
{
ProjectErrorUtilities.VerifyThrowInvalidProject(arguments?.Length == 2, elementLocation, "InvalidItemFunctionSyntax", functionName, arguments == null ? 0 : arguments.Length);

string metadataName = arguments[0];
string metadataValueToFind = arguments[1];

foreach (Pair<string, S> item in itemsOfType)
{
string metadataValue = null;

try
{
metadataValue = item.Value.GetMetadataValueEscaped(metadataName);
}
catch (Exception ex) when (ex is ArgumentException || ex is InvalidOperationException)
{
// Blank metadata name
ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "CannotEvaluateItemMetadata", metadataName, ex.Message);
}

if (metadataValue != null && String.Equals(metadataValue, metadataValueToFind, StringComparison.OrdinalIgnoreCase))
if (!String.Equals(metadataValue, metadataValueToFind, StringComparison.OrdinalIgnoreCase))
{
// return a result through the enumerator
yield return new Pair<string, S>(item.Key, item.Value);
Expand Down Expand Up @@ -2863,12 +2887,9 @@ internal static ItemTransformFunction GetItemTransformFunction(IElementLocation
{
metadataValue = item.Value.GetMetadataValueEscaped(metadataName);
}
catch (ArgumentException ex) // Blank metadata name
{
ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "CannotEvaluateItemMetadata", metadataName, ex.Message);
}
catch (InvalidOperationException ex)
catch (Exception ex) when (ex is ArgumentException || ex is InvalidOperationException)
{
// Blank metadata name
ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "CannotEvaluateItemMetadata", metadataName, ex.Message);
}

Expand Down

0 comments on commit a0468d8

Please sign in to comment.