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
143 changes: 143 additions & 0 deletions src/Build.UnitTests/BuildEventArgsDataEnumeration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Microsoft.Build.Logging;
using Microsoft.Build.Shared;
using Shouldly;
using Xunit;

namespace Microsoft.Build.UnitTests
{
public class BuildEventArgsDataEnumeration
{
[Fact]
public void SamplePropertiesEnumeration()
{
var projectFile = @"C:\foo\bar.proj";
var args = new ProjectEvaluationFinishedEventArgs(
ResourceUtilities.GetResourceString("EvaluationFinished"),
projectFile)
{
BuildEventContext = BuildEventContext.Invalid,
ProjectFile = @"C:\foo\bar.proj",
GlobalProperties = new Dictionary<string, string>() { { "GlobalKey", "GlobalValue" } },
Properties = new List<object>()
{
new DictionaryEntry("Key", "Value"),
ProjectPropertyInstance.Create("prop", "val"),
new KeyValuePair<string, string>("foo","bar")
},
Items = null
};

List<PropertyData> results = args.EnumerateProperties().ToList();
results.Count.ShouldBe(3);
results[0].ShouldBe(new("Key", "Value"));
results[1].ShouldBe(new("prop", "val"));
results[2].ShouldBe(new("foo", "bar"));
}

[Fact]
public void SampleItemsEnumeration()
{
string projectFile = @"C:\foo\bar.proj";
ProjectEvaluationFinishedEventArgs args = new ProjectEvaluationFinishedEventArgs(
ResourceUtilities.GetResourceString("EvaluationFinished"),
projectFile)
{
BuildEventContext = BuildEventContext.Invalid,
ProjectFile = @"C:\foo\bar.proj",
GlobalProperties = new Dictionary<string, string>() { { "GlobalKey", "GlobalValue" } },
Properties = null,
Items = new List<DictionaryEntry>()
{
new DictionaryEntry("Key", new MyTaskItem() { ItemSpec = "TestItemSpec" }),
new DictionaryEntry("Key2",
new TaskItemData("spec",
new Dictionary<string, string>() { { "metadat1", "val1" }, { "metadat2", "val2" } })),
}
};

List<ItemData> results = args.EnumerateItems().ToList();

results.Count.ShouldBe(2);
results[0].Type.ShouldBe("Key");
results[0].EvaluatedInclude.ShouldBe("TestItemSpec");
results[0].EnumerateMetadata().ShouldBeEmpty();

results[1].Type.ShouldBe("Key2");
results[1].EvaluatedInclude.ShouldBe("spec");
List<KeyValuePair<string, string>> metadata = results[1].EnumerateMetadata().ToList();
metadata.Count.ShouldBe(2);
metadata[0].Key.ShouldBe("metadat1");
metadata[0].Value.ShouldBe("val1");
metadata[1].Key.ShouldBe("metadat2");
metadata[1].Value.ShouldBe("val2");
}

[Fact]
public void SampleFilteredItemsEnumeration()
{
string projectFile = @"C:\foo\bar.proj";
ProjectEvaluationFinishedEventArgs args = new ProjectEvaluationFinishedEventArgs(
ResourceUtilities.GetResourceString("EvaluationFinished"),
projectFile)
{
BuildEventContext = BuildEventContext.Invalid,
ProjectFile = @"C:\foo\bar.proj",
GlobalProperties = new Dictionary<string, string>() { { "GlobalKey", "GlobalValue" } },
Properties = null,
Items = new List<DictionaryEntry>()
{
new DictionaryEntry("Key", new MyTaskItem() { ItemSpec = "TestItemSpec" }),
new DictionaryEntry("Key2",
new TaskItemData("spec",
new Dictionary<string, string>() { { "metadat1", "val1" }, { "metadat2", "val2" } })),
new DictionaryEntry("Key2", new MyTaskItem() { ItemSpec = "TestItemSpec3" }),
new DictionaryEntry("Key",
new TaskItemData("spec4",
new Dictionary<string, string>() { { "metadat41", "val41" }, { "metadat42", "val42" } })),
}
};

List<ItemData> results = args.EnumerateItemsOfType("Key").ToList();

results.Count.ShouldBe(2);
results[0].Type.ShouldBe("Key");
results[0].EvaluatedInclude.ShouldBe("TestItemSpec");
results[0].EnumerateMetadata().ShouldBeEmpty();

results[1].Type.ShouldBe("Key");
results[1].EvaluatedInclude.ShouldBe("spec4");
List<KeyValuePair<string, string>> metadata = results[1].EnumerateMetadata().ToList();
metadata.Count.ShouldBe(2);
metadata[0].Key.ShouldBe("metadat41");
metadata[0].Value.ShouldBe("val41");
metadata[1].Key.ShouldBe("metadat42");
metadata[1].Value.ShouldBe("val42");

results = args.EnumerateItemsOfType("Key2").ToList();

results.Count.ShouldBe(2);

results[0].Type.ShouldBe("Key2");
results[0].EvaluatedInclude.ShouldBe("spec");
metadata = results[0].EnumerateMetadata().ToList();
metadata.Count.ShouldBe(2);
metadata[0].Key.ShouldBe("metadat1");
metadata[0].Value.ShouldBe("val1");
metadata[1].Key.ShouldBe("metadat2");
metadata[1].Value.ShouldBe("val2");

results[1].Type.ShouldBe("Key2");
results[1].EvaluatedInclude.ShouldBe("TestItemSpec3");
results[1].EnumerateMetadata().ShouldBeEmpty();
}
}
}
12 changes: 1 addition & 11 deletions src/Build.UnitTests/MockTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -425,17 +425,7 @@ internal sealed class MyTaskItem : ITaskItem
{
#region ITaskItem Members

public string ItemSpec
{
get
{
return "foo";
}
set
{
// do nothing
}
}
public string ItemSpec { get; set; }

public ICollection MetadataNames
{
Expand Down
23 changes: 17 additions & 6 deletions src/Build/Collections/ItemDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,11 +172,7 @@ IEnumerator IEnumerable.GetEnumerator()
/// <summary>
/// Enumerates item lists per each item type under the lock.
/// </summary>
/// <param name="itemTypeCallback">
/// A delegate that accepts the item type string and a list of items of that type.
/// Will be called for each item type in the list.
/// </param>
public void EnumerateItemsPerType(Action<string, IEnumerable<T>> itemTypeCallback)
public IEnumerable<(string itemType, IEnumerable<T> itemValue)> EnumerateItemsPerType()
{
lock (_itemLists)
{
Expand All @@ -188,11 +184,26 @@ public void EnumerateItemsPerType(Action<string, IEnumerable<T>> itemTypeCallbac
continue;
}

itemTypeCallback(itemTypeBucket.Key, itemTypeBucket.Value);
yield return (itemTypeBucket.Key, itemTypeBucket.Value);
}
}
}

/// <summary>
/// Enumerates item lists per each item type under the lock.
/// </summary>
/// <param name="itemTypeCallback">
/// A delegate that accepts the item type string and a list of items of that type.
/// Will be called for each item type in the list.
/// </param>
public void EnumerateItemsPerType(Action<string, IEnumerable<T>> itemTypeCallback)
{
foreach (var tuple in EnumerateItemsPerType())
{
itemTypeCallback(tuple.itemType, tuple.itemValue);
}
}

#region ItemDictionary<T> Members

/// <summary>
Expand Down
13 changes: 11 additions & 2 deletions src/Build/Collections/PropertyDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;

#nullable disable
Expand Down Expand Up @@ -529,17 +530,25 @@ internal Dictionary<string, string> ToDictionary()
}
}

internal void Enumerate(Action<string, string> keyValueCallback)
internal IEnumerable<PropertyData> Enumerate()
{
lock (_properties)
{
foreach (var kvp in (ICollection<T>)_properties)
{
keyValueCallback(kvp.Key, EscapingUtilities.UnescapeAll(kvp.EscapedValue));
yield return new(kvp.Key, EscapingUtilities.UnescapeAll(kvp.EscapedValue));
}
}
}

internal void Enumerate(Action<string, string> keyValueCallback)
{
foreach (var property in Enumerate())
{
keyValueCallback(property.Name, property.Value);
}
}

internal IEnumerable<TResult> Filter<TResult>(Func<T, bool> filter, Func<T, TResult> selector)
{
List<TResult> result = new();
Expand Down
11 changes: 8 additions & 3 deletions src/Build/Definition/ProjectItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Linq;
using Microsoft.Build.Collections;
using Microsoft.Build.Construction;
using Microsoft.Build.Framework;
using Microsoft.Build.ObjectModelRemoting;
using Microsoft.Build.Shared;
using Microsoft.Build.Shared.FileSystem;
Expand All @@ -27,7 +28,7 @@ namespace Microsoft.Build.Evaluation
/// we do use it for build-time items.
/// </comment>
[DebuggerDisplay("{ItemType}={EvaluatedInclude} [{UnevaluatedInclude}] #DirectMetadata={DirectMetadataCount}")]
public class ProjectItem : IItem<ProjectMetadata>, IProjectMetadataParent
public class ProjectItem : IItem<ProjectMetadata>, IProjectMetadataParent, IItemData
{
/// <summary>
/// Project that this item lives in.
Expand Down Expand Up @@ -143,6 +144,9 @@ internal ProjectItem(

internal virtual ProjectItemLink Link => null;

/// <inheritdoc cref="IItemData.EnumerateMetadata"/>
IEnumerable<KeyValuePair<string, string>> IItemData.EnumerateMetadata() => Metadata.Select(m => new KeyValuePair<string, string>(m.Name, m.EvaluatedValue));

/// <summary>
/// Backing XML item.
/// Can never be null.
Expand Down Expand Up @@ -193,9 +197,10 @@ public string UnevaluatedInclude
}
}

/// <summary>
/// <inheritdoc cref="IItemData.EvaluatedInclude"/>
/// <remarks>
/// Gets the evaluated value of the include, unescaped.
/// </summary>
/// </remarks>
public string EvaluatedInclude
{
[DebuggerStepThrough]
Expand Down
11 changes: 8 additions & 3 deletions src/Build/Instance/ProjectItemInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ public class ProjectItemInstance :
IMetadataTable,
ITranslatable,
IMetadataContainer,
IItemTypeDefinition
IItemTypeDefinition,
IItemData
{
/// <summary>
/// The project instance to which this item belongs.
Expand Down Expand Up @@ -184,10 +185,11 @@ public string ItemType
{ return _itemType; }
}

/// <summary>
/// <inheritdoc cref="IItemData.EvaluatedInclude"/>
/// <remarks>
/// Evaluated include value.
/// May be empty string.
/// </summary>
/// </remarks>
public string EvaluatedInclude
{
[DebuggerStepThrough]
Expand Down Expand Up @@ -301,6 +303,9 @@ string ITaskItem.ItemSpec
}
}

/// <inheritdoc cref="IItemData.EnumerateMetadata"/>
IEnumerable<KeyValuePair<string, string>> IItemData.EnumerateMetadata() => ((IMetadataContainer)this).EnumerateMetadata();

/// <summary>
/// ITaskItem implementation
/// </summary>
Expand Down
73 changes: 73 additions & 0 deletions src/Build/Logging/BuildEventArgsExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// 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 System.Collections.Generic;
using Microsoft.Build.Framework;

namespace Microsoft.Build.Logging;

/// <summary>
/// Helper extension methods for working with data passed via
/// <see cref="ProjectEvaluationFinishedEventArgs"/> and <see cref="ProjectStartedEventArgs"/>
/// </summary>
public static class BuildEventArgsExtensions
{
/// <summary>
/// Lazy enumerates and strong types properties from Properties property.
/// </summary>
public static IEnumerable<PropertyData> EnumerateProperties(
this ProjectEvaluationFinishedEventArgs eventArgs)
=> EnumerateProperties(eventArgs.Properties);

/// <summary>
/// Lazy enumerates and strong types properties from Properties property.
/// </summary>
public static IEnumerable<PropertyData> EnumerateProperties(
this ProjectStartedEventArgs eventArgs)
=> EnumerateProperties(eventArgs.Properties);

/// <summary>
/// Lazy enumerates and partially strong types items from Items property.
/// The actual item value is proxied via accessor methods - to be able to provide defined interface
/// </summary>
/// <returns></returns>
public static IEnumerable<ItemData> EnumerateItems(
this ProjectEvaluationFinishedEventArgs eventArgs)
=> EnumerateItems(eventArgs.Items);

/// <summary>
/// Lazy enumerates and partially strong types items from Items property. Only items with matching type will be returned (case-insensitive, MSBuild valid names only).
/// The actual item value is proxied via accessor methods - to be able to provide defined interface
/// </summary>
/// <returns></returns>
public static IEnumerable<ItemData> EnumerateItemsOfType(
this ProjectEvaluationFinishedEventArgs eventArgs, string typeName)
=> EnumerateItemsOfType(eventArgs.Items, typeName);

/// <summary>
/// Lazy enumerates and strong types items from Items property.
/// The actual item value is proxied via accessor methods - to be able to provide defined interface
/// </summary>
public static IEnumerable<ItemData> EnumerateItems(
this ProjectStartedEventArgs eventArgs)
=> EnumerateItems(eventArgs.Items);

/// <summary>
/// Lazy enumerates and partially strong types items from Items property. Only items with matching type will be returned (case-insensitive, MSBuild valid names only).
/// The actual item value is proxied via accessor methods - to be able to provide defined interface
/// </summary>
/// <returns></returns>
public static IEnumerable<ItemData> EnumerateItemsOfType(
this ProjectStartedEventArgs eventArgs, string typeName)
=> EnumerateItemsOfType(eventArgs.Items, typeName);

private static IEnumerable<PropertyData> EnumerateProperties(IEnumerable? properties)
=> Internal.Utilities.EnumerateProperties(properties);

private static IEnumerable<ItemData> EnumerateItems(IEnumerable? items)
=> Internal.Utilities.EnumerateItems(items);

private static IEnumerable<ItemData> EnumerateItemsOfType(IEnumerable? items, string typeName)
=> Internal.Utilities.EnumerateItemsOfType(items, typeName);
}
1 change: 1 addition & 0 deletions src/Build/Microsoft.Build.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@
<Compile Include="FileAccess\ReportedFileOperation.cs" />
<Compile Include="FileAccess\RequestedAccess.cs" />
<Compile Include="Instance\IPropertyElementWithLocation.cs" />
<Compile Include="Logging\BuildEventArgsExtensions.cs" />
<Compile Include="Utilities\ReaderWriterLockSlimExtensions.cs" />
<Compile Include="BackEnd\Node\ConsoleOutput.cs" />
<Compile Include="BackEnd\Node\PartialBuildTelemetry.cs" />
Expand Down
Loading