diff --git a/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs b/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs
index 239f7910b68..03d3c5e61c8 100644
--- a/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs
+++ b/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs
@@ -38,6 +38,7 @@ public abstract partial class BuildEventArgs : System.EventArgs
public Microsoft.Build.Framework.BuildEventContext BuildEventContext { get { throw null; } set { } }
public string HelpKeyword { get { throw null; } }
public virtual string Message { get { throw null; } protected set { } }
+ protected System.DateTime RawTimestamp { get { throw null; } set { } }
public string SenderName { get { throw null; } }
public int ThreadId { get { throw null; } }
public System.DateTime Timestamp { get { throw null; } }
@@ -589,6 +590,22 @@ public partial class TaskFinishedEventArgs : Microsoft.Build.Framework.BuildStat
public string TaskName { get { throw null; } }
}
public delegate void TaskFinishedEventHandler(object sender, Microsoft.Build.Framework.TaskFinishedEventArgs e);
+ public partial class TaskParameterEventArgs : Microsoft.Build.Framework.BuildMessageEventArgs
+ {
+ public TaskParameterEventArgs(Microsoft.Build.Framework.TaskParameterMessageKind kind, string itemType, System.Collections.IList items, bool logItemMetadata, System.DateTime eventTimestamp) { }
+ public System.Collections.IList Items { get { throw null; } }
+ public string ItemType { get { throw null; } }
+ public Microsoft.Build.Framework.TaskParameterMessageKind Kind { get { throw null; } }
+ public bool LogItemMetadata { get { throw null; } }
+ public override string Message { get { throw null; } }
+ }
+ public enum TaskParameterMessageKind
+ {
+ TaskInput = 0,
+ TaskOutput = 1,
+ AddItem = 2,
+ RemoveItem = 3,
+ }
public partial class TaskPropertyInfo
{
public TaskPropertyInfo(string name, System.Type typeOfParameter, bool output, bool required) { }
diff --git a/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs b/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs
index 9c2d45f2253..31d90e78d65 100644
--- a/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs
+++ b/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs
@@ -38,6 +38,7 @@ public abstract partial class BuildEventArgs : System.EventArgs
public Microsoft.Build.Framework.BuildEventContext BuildEventContext { get { throw null; } set { } }
public string HelpKeyword { get { throw null; } }
public virtual string Message { get { throw null; } protected set { } }
+ protected System.DateTime RawTimestamp { get { throw null; } set { } }
public string SenderName { get { throw null; } }
public int ThreadId { get { throw null; } }
public System.DateTime Timestamp { get { throw null; } }
@@ -588,6 +589,22 @@ public partial class TaskFinishedEventArgs : Microsoft.Build.Framework.BuildStat
public string TaskName { get { throw null; } }
}
public delegate void TaskFinishedEventHandler(object sender, Microsoft.Build.Framework.TaskFinishedEventArgs e);
+ public partial class TaskParameterEventArgs : Microsoft.Build.Framework.BuildMessageEventArgs
+ {
+ public TaskParameterEventArgs(Microsoft.Build.Framework.TaskParameterMessageKind kind, string itemType, System.Collections.IList items, bool logItemMetadata, System.DateTime eventTimestamp) { }
+ public System.Collections.IList Items { get { throw null; } }
+ public string ItemType { get { throw null; } }
+ public Microsoft.Build.Framework.TaskParameterMessageKind Kind { get { throw null; } }
+ public bool LogItemMetadata { get { throw null; } }
+ public override string Message { get { throw null; } }
+ }
+ public enum TaskParameterMessageKind
+ {
+ TaskInput = 0,
+ TaskOutput = 1,
+ AddItem = 2,
+ RemoveItem = 3,
+ }
public partial class TaskPropertyInfo
{
public TaskPropertyInfo(string name, System.Type typeOfParameter, bool output, bool required) { }
diff --git a/src/Build.UnitTests/BackEnd/EventSourceSink_Tests.cs b/src/Build.UnitTests/BackEnd/EventSourceSink_Tests.cs
index 3798326b7e2..8dc2e82dbdd 100644
--- a/src/Build.UnitTests/BackEnd/EventSourceSink_Tests.cs
+++ b/src/Build.UnitTests/BackEnd/EventSourceSink_Tests.cs
@@ -68,6 +68,7 @@ public void ConsumeEventsGoodEventsNoHandlers()
eventHelper.RaiseBuildEvent(RaiseEventHelper.NormalMessage);
eventHelper.RaiseBuildEvent(RaiseEventHelper.TaskFinished);
eventHelper.RaiseBuildEvent(RaiseEventHelper.CommandLine);
+ eventHelper.RaiseBuildEvent(RaiseEventHelper.TaskParameter);
eventHelper.RaiseBuildEvent(RaiseEventHelper.Warning);
eventHelper.RaiseBuildEvent(RaiseEventHelper.Error);
eventHelper.RaiseBuildEvent(RaiseEventHelper.TargetStarted);
@@ -99,6 +100,7 @@ public void LoggerExceptionInEventHandler()
RaiseExceptionInEventHandler(RaiseEventHelper.NormalMessage, exception);
RaiseExceptionInEventHandler(RaiseEventHelper.TaskFinished, exception);
RaiseExceptionInEventHandler(RaiseEventHelper.CommandLine, exception);
+ RaiseExceptionInEventHandler(RaiseEventHelper.TaskParameter, exception);
RaiseExceptionInEventHandler(RaiseEventHelper.Warning, exception);
RaiseExceptionInEventHandler(RaiseEventHelper.Error, exception);
RaiseExceptionInEventHandler(RaiseEventHelper.TargetStarted, exception);
@@ -733,6 +735,11 @@ internal class RaiseEventHelper
///
private static TaskCommandLineEventArgs s_taskCommandLine = new TaskCommandLineEventArgs("commandLine", "taskName", MessageImportance.Low);
+ ///
+ /// Task Parameter Event
+ ///
+ private static TaskParameterEventArgs s_taskParameter = new TaskParameterEventArgs(TaskParameterMessageKind.TaskInput, "ItemName", null, true, DateTime.MinValue);
+
///
/// Build Warning Event
///
@@ -883,6 +890,11 @@ internal static TaskCommandLineEventArgs CommandLine
}
}
+ ///
+ /// Event which can be raised in multiple tests.
+ ///
+ internal static TaskParameterEventArgs TaskParameter => s_taskParameter;
+
///
/// Event which can be raised in multiple tests.
///
diff --git a/src/Build.UnitTests/BackEnd/NodePackets_Tests.cs b/src/Build.UnitTests/BackEnd/NodePackets_Tests.cs
index 459d835bd25..3e860fc7d5f 100644
--- a/src/Build.UnitTests/BackEnd/NodePackets_Tests.cs
+++ b/src/Build.UnitTests/BackEnd/NodePackets_Tests.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
+using System.Linq;
using Microsoft.Build.Framework;
using Microsoft.Build.BackEnd;
using Microsoft.Build.Shared;
@@ -44,6 +45,7 @@ public void VerifyEventType()
TaskStartedEventArgs taskStarted = new TaskStartedEventArgs("message", "help", "projectFile", "taskFile", "taskName");
TaskFinishedEventArgs taskFinished = new TaskFinishedEventArgs("message", "help", "projectFile", "taskFile", "taskName", true);
TaskCommandLineEventArgs commandLine = new TaskCommandLineEventArgs("commandLine", "taskName", MessageImportance.Low);
+ TaskParameterEventArgs taskParameter = CreateTaskParameter();
BuildWarningEventArgs warning = new BuildWarningEventArgs("SubCategoryForSchemaValidationErrors", "MSB4000", "file", 1, 2, 3, 4, "message", "help", "sender");
BuildErrorEventArgs error = new BuildErrorEventArgs("SubCategoryForSchemaValidationErrors", "MSB4000", "file", 1, 2, 3, 4, "message", "help", "sender");
TargetStartedEventArgs targetStarted = new TargetStartedEventArgs("message", "help", "targetName", "ProjectFile", "targetFile");
@@ -58,6 +60,7 @@ public void VerifyEventType()
VerifyLoggingPacket(taskStarted, LoggingEventType.TaskStartedEvent);
VerifyLoggingPacket(taskFinished, LoggingEventType.TaskFinishedEvent);
VerifyLoggingPacket(commandLine, LoggingEventType.TaskCommandLineEvent);
+ VerifyLoggingPacket(taskParameter, LoggingEventType.TaskParameterEvent);
VerifyLoggingPacket(warning, LoggingEventType.BuildWarningEvent);
VerifyLoggingPacket(error, LoggingEventType.BuildErrorEvent);
VerifyLoggingPacket(targetStarted, LoggingEventType.TargetStartedEvent);
@@ -67,12 +70,41 @@ public void VerifyEventType()
VerifyLoggingPacket(externalStartedEvent, LoggingEventType.CustomEvent);
}
+ private static TaskParameterEventArgs CreateTaskParameter()
+ {
+ var items = new TaskItemData[]
+ {
+ new TaskItemData("ItemSpec1", null),
+ new TaskItemData("ItemSpec2", Enumerable.Range(1,3).ToDictionary(i => i.ToString(), i => i.ToString() + "value"))
+ };
+ var result = new TaskParameterEventArgs(
+ TaskParameterMessageKind.TaskInput,
+ "ItemName",
+ items,
+ logItemMetadata: true,
+ DateTime.MinValue);
+
+ // normalize line endings as we can't rely on the line endings of NodePackets_Tests.cs
+ Assert.Equal(@"Task Parameter:
+ ItemName=
+ ItemSpec1
+ ItemSpec2
+ 1=1value
+ 2=2value
+ 3=3value".Replace("\r\n", "\n"), result.Message);
+
+ return result;
+ }
+
///
/// Tests serialization of LogMessagePacket with each kind of event type.
///
[Fact]
public void TestTranslation()
{
+ // need to touch the type so that the static constructor runs
+ _ = ItemGroupLoggingHelper.OutputItemParameterMessagePrefix;
+
TaskItem item = new TaskItem("Hello", "my.proj");
List targetOutputs = new List();
targetOutputs.Add(item);
@@ -88,6 +120,7 @@ public void TestTranslation()
new TaskStartedEventArgs("message", "help", "projectFile", "taskFile", "taskName"),
new TaskFinishedEventArgs("message", "help", "projectFile", "taskFile", "taskName", true),
new TaskCommandLineEventArgs("commandLine", "taskName", MessageImportance.Low),
+ CreateTaskParameter(),
new BuildWarningEventArgs("SubCategoryForSchemaValidationErrors", "MSB4000", "file", 1, 2, 3, 4, "message", "help", "sender"),
new BuildErrorEventArgs("SubCategoryForSchemaValidationErrors", "MSB4000", "file", 1, 2, 3, 4, "message", "help", "sender"),
new TargetStartedEventArgs("message", "help", "targetName", "ProjectFile", "targetFile"),
@@ -281,6 +314,19 @@ private void CompareLogMessagePackets(LogMessagePacket left, LogMessagePacket ri
Assert.Equal(leftCommand.TaskName, rightCommand.TaskName);
break;
+ case LoggingEventType.TaskParameterEvent:
+ var leftTaskParameter = left.NodeBuildEvent.Value.Value as TaskParameterEventArgs;
+ var rightTaskParameter = right.NodeBuildEvent.Value.Value as TaskParameterEventArgs;
+ Assert.NotNull(leftTaskParameter);
+ Assert.NotNull(rightTaskParameter);
+ Assert.Equal(leftTaskParameter.Kind, rightTaskParameter.Kind);
+ Assert.Equal(leftTaskParameter.ItemType, rightTaskParameter.ItemType);
+ Assert.Equal(leftTaskParameter.Items.Count, rightTaskParameter.Items.Count);
+ Assert.Equal(leftTaskParameter.Message, rightTaskParameter.Message);
+ Assert.Equal(leftTaskParameter.BuildEventContext, rightTaskParameter.BuildEventContext);
+ Assert.Equal(leftTaskParameter.Timestamp, rightTaskParameter.Timestamp);
+ break;
+
case LoggingEventType.TaskFinishedEvent:
TaskFinishedEventArgs leftTaskFinished = left.NodeBuildEvent.Value.Value as TaskFinishedEventArgs;
TaskFinishedEventArgs rightTaskFinished = right.NodeBuildEvent.Value.Value as TaskFinishedEventArgs;
diff --git a/src/Build.UnitTests/BinaryLogger_Tests.cs b/src/Build.UnitTests/BinaryLogger_Tests.cs
index ec95009bec1..8deb4db94b7 100644
--- a/src/Build.UnitTests/BinaryLogger_Tests.cs
+++ b/src/Build.UnitTests/BinaryLogger_Tests.cs
@@ -7,7 +7,7 @@ namespace Microsoft.Build.UnitTests
{
public class BinaryLoggerTests : IDisposable
{
- private static string s_testProject = @"
+ private const string s_testProject = @"
Test
@@ -22,6 +22,38 @@ public class BinaryLoggerTests : IDisposable
";
+
+ private const string s_testProject2 = @"
+
+
+
+
+
+
+ fromItemDefinition%61%62%63<>
+
+
+
+
+
+ MetadataValue1%61%62%63<>
+
+
+
+
+ custom%61%62%63<>
+
+
+
+
+
+
+
+
+
+
+ ";
+
private readonly TestEnvironment _env;
private string _logFile;
@@ -35,8 +67,10 @@ public BinaryLoggerTests(ITestOutputHelper output)
_logFile = _env.ExpectFile(".binlog").Path;
}
- [Fact]
- public void TestBinaryLoggerRoundtrip()
+ [Theory]
+ [InlineData(s_testProject)]
+ [InlineData(s_testProject2)]
+ public void TestBinaryLoggerRoundtrip(string projectText)
{
var binaryLogger = new BinaryLogger();
@@ -45,14 +79,14 @@ public void TestBinaryLoggerRoundtrip()
var mockLogFromBuild = new MockLogger();
// build and log into binary logger and mockLogger1
- ObjectModelHelpers.BuildProjectExpectSuccess(s_testProject, binaryLogger, mockLogFromBuild);
+ ObjectModelHelpers.BuildProjectExpectSuccess(projectText, binaryLogger, mockLogFromBuild);
var mockLogFromPlayback = new MockLogger();
var binaryLogReader = new BinaryLogReplayEventSource();
mockLogFromPlayback.Initialize(binaryLogReader);
- // read the binary log and replay into mockLogger2testassembly
+ // read the binary log and replay into mockLogger2
binaryLogReader.Replay(_logFile);
// the binlog will have more information than recorded by the text log
diff --git a/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs b/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs
index afd56aea915..a2874d96c06 100644
--- a/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs
+++ b/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs
@@ -1,9 +1,10 @@
-using System;
+using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
+using Microsoft.Build.BackEnd;
using Microsoft.Build.Framework;
using Microsoft.Build.Framework.Profiler;
using Microsoft.Build.Logging;
@@ -70,21 +71,39 @@ public void RoundtripProjectStartedEventArgs()
toolsVersion: "Current");
args.BuildEventContext = new BuildEventContext(1, 2, 3, 4, 5, 6);
- Roundtrip(args,
+ Roundtrip(args,
e => ToString(e.BuildEventContext),
e => ToString(e.GlobalProperties),
- e => ToString(e.Items.OfType().ToDictionary(d => d.Key.ToString(), d => ((ITaskItem)d.Value).ItemSpec)),
+ e => GetItemsString(e.Items),
e => e.Message,
e => ToString(e.ParentProjectBuildEventContext),
e => e.ProjectFile,
e => e.ProjectId.ToString(),
- e => ToString(e.Properties.OfType().ToDictionary(d => d.Key.ToString(), d => d.Value.ToString())),
+ e => ToString(e.Properties.OfType().ToDictionary((Func)(d => d.Key.ToString()), (Func)(d => d.Value.ToString()))),
e => e.TargetNames,
e => e.ThreadId.ToString(),
e => e.Timestamp.ToString(),
e => e.ToolsVersion);
}
+ private string GetItemsString(IEnumerable items)
+ {
+ return ToString(items.OfType().ToDictionary(d => d.Key.ToString(), d => GetTaskItemString((ITaskItem)d.Value)));
+ }
+
+ private string GetTaskItemString(ITaskItem taskItem)
+ {
+ var sb = new StringBuilder();
+ sb.Append(taskItem.ItemSpec);
+ foreach (string name in taskItem.MetadataNames)
+ {
+ var value = taskItem.GetMetadata(name);
+ sb.Append($";{name}={value}");
+ }
+
+ return sb.ToString();
+ }
+
[Fact]
public void RoundtripProjectFinishedEventArgs()
{
@@ -309,6 +328,23 @@ public void RoundtripTaskCommandLineEventArgs()
e => e.Subcategory);
}
+ [Fact]
+ public void RoundtripTaskParameterEventArgs()
+ {
+ var items = new TaskItemData[]
+ {
+ new TaskItemData("ItemSpec1", null),
+ new TaskItemData("ItemSpec2", Enumerable.Range(1,3).ToDictionary(i => i.ToString(), i => i.ToString() + "value"))
+ };
+ var args = new TaskParameterEventArgs(TaskParameterMessageKind.TaskOutput, "ItemName", items, true, DateTime.MinValue);
+
+ Roundtrip(args,
+ e => e.Kind.ToString(),
+ e => e.ItemType,
+ e => e.LogItemMetadata.ToString(),
+ e => GetItemsString(e.Items));
+ }
+
[Fact]
public void RoundtripProjectEvaluationStartedEventArgs()
{
diff --git a/src/Build.UnitTests/ConfigureableForwardingLogger_Tests.cs b/src/Build.UnitTests/ConfigureableForwardingLogger_Tests.cs
index f584072937b..3698f90ed5d 100644
--- a/src/Build.UnitTests/ConfigureableForwardingLogger_Tests.cs
+++ b/src/Build.UnitTests/ConfigureableForwardingLogger_Tests.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+using System;
using System.Collections.Generic;
using Microsoft.Build.Framework;
using Microsoft.Build.Logging;
@@ -20,6 +21,7 @@ public class ConfigureableForwardingLogger_Tests
private readonly TaskStartedEventArgs _taskStarted = new TaskStartedEventArgs("message", "help", "projectFile", "taskFile", "taskName");
private readonly TaskFinishedEventArgs _taskFinished = new TaskFinishedEventArgs("message", "help", "projectFile", "taskFile", "taskName", true);
private readonly TaskCommandLineEventArgs _commandLine = new TaskCommandLineEventArgs("commandLine", "taskName", MessageImportance.Low);
+ private readonly TaskParameterEventArgs _taskParameter = new TaskParameterEventArgs(TaskParameterMessageKind.TaskInput, "ItemName", null, true, DateTime.MinValue);
private readonly BuildWarningEventArgs _warning = new BuildWarningEventArgs("SubCategoryForSchemaValidationErrors", "MSB4000", "file", 1, 2, 3, 4, "message", "help", "sender");
private readonly BuildErrorEventArgs _error = new BuildErrorEventArgs("SubCategoryForSchemaValidationErrors", "MSB4000", "file", 1, 2, 3, 4, "message", "help", "sender");
private readonly TargetStartedEventArgs _targetStarted = new TargetStartedEventArgs("message", "help", "targetName", "ProjectFile", "targetFile");
@@ -131,6 +133,7 @@ public void ForwardingLoggingEventsBasedOnVerbosity(LoggerVerbosity? loggerVerbo
_normalMessage,
_highMessage,
_commandLine,
+ _taskParameter,
_warning,
_error,
_taskFinished,
@@ -150,6 +153,7 @@ public void ForwardingLoggingEventsBasedOnVerbosity(LoggerVerbosity? loggerVerbo
_normalMessage,
_highMessage,
_commandLine,
+ _taskParameter,
_externalStartedEvent,
_warning,
_error,
@@ -266,6 +270,7 @@ private void RaiseEvents(EventSourceSink source)
source.Consume(_normalMessage);
source.Consume(_highMessage);
source.Consume(_commandLine);
+ source.Consume(_taskParameter);
source.Consume(_externalStartedEvent);
source.Consume(_warning);
source.Consume(_error);
diff --git a/src/Build/BackEnd/Components/Logging/LoggingService.cs b/src/Build/BackEnd/Components/Logging/LoggingService.cs
index 85c95d728d1..074ec86ccca 100644
--- a/src/Build/BackEnd/Components/Logging/LoggingService.cs
+++ b/src/Build/BackEnd/Components/Logging/LoggingService.cs
@@ -285,6 +285,10 @@ protected LoggingService(LoggerMode loggerMode, int nodeId)
CreateLoggingEventQueue();
}
+ // Ensure the static constructor of ItemGroupLoggingHelper runs.
+ // It is important to ensure the Message delegate on TaskParameterEventArgs is set.
+ _ = ItemGroupLoggingHelper.ItemGroupIncludeLogMessagePrefix;
+
_serviceState = LoggingServiceState.Instantiated;
}
diff --git a/src/Build/BackEnd/Components/Logging/TargetLoggingContext.cs b/src/Build/BackEnd/Components/Logging/TargetLoggingContext.cs
index 6daf7608f20..efde35dd8af 100644
--- a/src/Build/BackEnd/Components/Logging/TargetLoggingContext.cs
+++ b/src/Build/BackEnd/Components/Logging/TargetLoggingContext.cs
@@ -138,6 +138,11 @@ internal TargetOutputItemsInstanceEnumeratorProxy(IEnumerable backingI
_backingItems = backingItems;
}
+ // For performance reasons we need to expose the raw items to BinaryLogger
+ // as we know we're not going to mutate anything. This allows us to bypass DeepClone
+ // for each item
+ internal IEnumerable BackingItems => _backingItems;
+
///
/// Returns an enumerator that provides copies of the items
/// in the backing store.
diff --git a/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupIntrinsicTask.cs b/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupIntrinsicTask.cs
index 9b24ebd0cec..66195775b22 100644
--- a/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupIntrinsicTask.cs
+++ b/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupIntrinsicTask.cs
@@ -213,12 +213,12 @@ private void ExecuteAdd(ProjectItemGroupTaskItemInstance child, ItemBucket bucke
if (LogTaskInputs && !LoggingContext.LoggingService.OnlyLogCriticalEvents && itemsToAdd?.Count > 0)
{
- var itemGroupText = ItemGroupLoggingHelper.GetParameterText(
- ItemGroupLoggingHelper.ItemGroupIncludeLogMessagePrefix,
+ ItemGroupLoggingHelper.LogTaskParameter(
+ LoggingContext,
+ TaskParameterMessageKind.AddItem,
child.ItemType,
itemsToAdd,
logItemMetadata: true);
- LoggingContext.LogCommentFromText(MessageImportance.Low, itemGroupText);
}
// Now add the items we created to the lookup.
@@ -256,12 +256,12 @@ private void ExecuteRemove(ProjectItemGroupTaskItemInstance child, ItemBucket bu
{
if (LogTaskInputs && !LoggingContext.LoggingService.OnlyLogCriticalEvents && itemsToRemove.Count > 0)
{
- var itemGroupText = ItemGroupLoggingHelper.GetParameterText(
- ItemGroupLoggingHelper.ItemGroupRemoveLogMessage,
+ ItemGroupLoggingHelper.LogTaskParameter(
+ LoggingContext,
+ TaskParameterMessageKind.RemoveItem,
child.ItemType,
itemsToRemove,
logItemMetadata: true);
- LoggingContext.LogCommentFromText(MessageImportance.Low, itemGroupText);
}
bucket.Lookup.RemoveItems(itemsToRemove);
diff --git a/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupLoggingHelper.cs b/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupLoggingHelper.cs
index 9aa0bfd4fce..2b8d80d306c 100644
--- a/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupLoggingHelper.cs
+++ b/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupLoggingHelper.cs
@@ -5,6 +5,8 @@
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
+using Microsoft.Build.BackEnd.Logging;
+using Microsoft.Build.Collections;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Microsoft.Build.Utilities;
@@ -31,6 +33,17 @@ internal static class ItemGroupLoggingHelper
internal static string OutputItemParameterMessagePrefix = ResourceUtilities.GetResourceString("OutputItemParameterMessagePrefix");
internal static string TaskParameterPrefix = ResourceUtilities.GetResourceString("TaskParameterPrefix");
+ ///
+ /// by itself doesn't have the implementation
+ /// to materialize the Message as that's a declaration assembly. We inject the logic
+ /// here.
+ ///
+ static ItemGroupLoggingHelper()
+ {
+ TaskParameterEventArgs.MessageGetter = GetTaskParameterText;
+ TaskParameterEventArgs.DictionaryFactory = ArrayDictionary.Create;
+ }
+
///
/// Gets a text serialized value of a parameter for logging.
///
@@ -203,5 +216,62 @@ private static void AppendStringFromParameterValue(ReuseableStringBuilder sb, ob
ErrorUtilities.ThrowInternalErrorUnreachable();
}
}
+
+ internal static void LogTaskParameter(
+ LoggingContext loggingContext,
+ TaskParameterMessageKind messageKind,
+ string itemType,
+ IList items,
+ bool logItemMetadata)
+ {
+ var args = CreateTaskParameterEventArgs(
+ loggingContext.BuildEventContext,
+ messageKind,
+ itemType,
+ items,
+ logItemMetadata,
+ DateTime.UtcNow);
+ loggingContext.LogBuildEvent(args);
+ }
+
+ internal static TaskParameterEventArgs CreateTaskParameterEventArgs(
+ BuildEventContext buildEventContext,
+ TaskParameterMessageKind messageKind,
+ string itemType,
+ IList items,
+ bool logItemMetadata,
+ DateTime timestamp)
+ {
+ var args = new TaskParameterEventArgs(
+ messageKind,
+ itemType,
+ items,
+ logItemMetadata,
+ timestamp);
+ args.BuildEventContext = buildEventContext;
+ return args;
+ }
+
+ internal static string GetTaskParameterText(TaskParameterEventArgs args)
+ => GetTaskParameterText(args.Kind, args.ItemType, args.Items, args.LogItemMetadata);
+
+ internal static string GetTaskParameterText(TaskParameterMessageKind messageKind, string itemType, IList items, bool logItemMetadata)
+ {
+ var resourceText = messageKind switch
+ {
+ TaskParameterMessageKind.AddItem => ItemGroupIncludeLogMessagePrefix,
+ TaskParameterMessageKind.RemoveItem => ItemGroupRemoveLogMessage,
+ TaskParameterMessageKind.TaskInput => TaskParameterPrefix,
+ TaskParameterMessageKind.TaskOutput => OutputItemParameterMessagePrefix,
+ _ => throw new NotImplementedException($"Unsupported {nameof(TaskParameterMessageKind)} value: {messageKind}")
+ };
+
+ var itemGroupText = GetParameterText(
+ resourceText,
+ itemType,
+ items,
+ logItemMetadata);
+ return itemGroupText;
+ }
}
}
diff --git a/src/Build/BackEnd/TaskExecutionHost/TaskExecutionHost.cs b/src/Build/BackEnd/TaskExecutionHost/TaskExecutionHost.cs
index 7bf9a69aebc..dc75cd9496e 100644
--- a/src/Build/BackEnd/TaskExecutionHost/TaskExecutionHost.cs
+++ b/src/Build/BackEnd/TaskExecutionHost/TaskExecutionHost.cs
@@ -1333,12 +1333,12 @@ private bool InternalSetTaskParameter(TaskPropertyInfo parameter, IList paramete
parameterValue.Count > 0 &&
parameter.Log)
{
- string parameterText = ItemGroupLoggingHelper.GetParameterText(
- ItemGroupLoggingHelper.TaskParameterPrefix,
+ ItemGroupLoggingHelper.LogTaskParameter(
+ _taskLoggingContext,
+ TaskParameterMessageKind.TaskInput,
parameter.Name,
parameterValue,
parameter.LogItemMetadata);
- _taskLoggingContext.LogCommentFromText(MessageImportance.Low, parameterText);
}
return InternalSetTaskParameter(parameter, (object)parameterValue);
@@ -1426,11 +1426,16 @@ private void GatherTaskItemOutputs(bool outputTargetIsItem, string outputTargetN
{
if (outputTargetIsItem)
{
+ // Only count non-null elements. We sometimes have a single-element array where the element is null
+ bool hasElements = false;
+
foreach (ITaskItem output in outputs)
{
// if individual items in the array are null, ignore them
if (output != null)
{
+ hasElements = true;
+
ProjectItemInstance newItem;
TaskItem outputAsProjectItem = output as TaskItem;
@@ -1474,15 +1479,14 @@ private void GatherTaskItemOutputs(bool outputTargetIsItem, string outputTargetN
}
}
- if (LogTaskInputs && !_taskLoggingContext.LoggingService.OnlyLogCriticalEvents && outputs.Length > 0 && parameter.Log)
+ if (hasElements && LogTaskInputs && !_taskLoggingContext.LoggingService.OnlyLogCriticalEvents && parameter.Log)
{
- string parameterText = ItemGroupLoggingHelper.GetParameterText(
- ItemGroupLoggingHelper.OutputItemParameterMessagePrefix,
+ ItemGroupLoggingHelper.LogTaskParameter(
+ _taskLoggingContext,
+ TaskParameterMessageKind.TaskOutput,
outputTargetName,
outputs,
parameter.LogItemMetadata);
-
- _taskLoggingContext.LogCommentFromText(MessageImportance.Low, parameterText);
}
}
else
@@ -1553,12 +1557,12 @@ private void GatherArrayStringAndValueOutputs(bool outputTargetIsItem, string ou
if (LogTaskInputs && !_taskLoggingContext.LoggingService.OnlyLogCriticalEvents && outputs.Length > 0 && parameter.Log)
{
- string parameterText = ItemGroupLoggingHelper.GetParameterText(
- ItemGroupLoggingHelper.OutputItemParameterMessagePrefix,
+ ItemGroupLoggingHelper.LogTaskParameter(
+ _taskLoggingContext,
+ TaskParameterMessageKind.TaskOutput,
outputTargetName,
outputs,
parameter.LogItemMetadata);
- _taskLoggingContext.LogCommentFromText(MessageImportance.Low, parameterText);
}
}
else
diff --git a/src/Build/Collections/ArrayDictionary.cs b/src/Build/Collections/ArrayDictionary.cs
new file mode 100644
index 00000000000..9cdf1d7a0e1
--- /dev/null
+++ b/src/Build/Collections/ArrayDictionary.cs
@@ -0,0 +1,261 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Microsoft.Build.Collections
+{
+ ///
+ /// Lightweight, read-only IDictionary implementation using two arrays
+ /// and O(n) lookup.
+ /// Requires specifying capacity at construction and does not
+ /// support reallocation to increase capacity.
+ ///
+ /// Type of keys
+ /// Type of values
+ internal class ArrayDictionary : IDictionary, IDictionary
+ {
+ private TKey[] keys;
+ private TValue[] values;
+
+ private int count;
+
+ public ArrayDictionary(int capacity)
+ {
+ keys = new TKey[capacity];
+ values = new TValue[capacity];
+ }
+
+ public static IDictionary Create(int capacity)
+ {
+ return new ArrayDictionary(capacity);
+ }
+
+ public TValue this[TKey key]
+ {
+ get
+ {
+ TryGetValue(key, out var value);
+ return value;
+ }
+
+ set
+ {
+ var comparer = KeyComparer;
+ for (int i = 0; i < count; i++)
+ {
+ if (comparer.Equals(key, keys[i]))
+ {
+ values[i] = value;
+ return;
+ }
+ }
+
+ Add(key, value);
+ }
+ }
+
+ object IDictionary.this[object key]
+ {
+ get => this[(TKey)key];
+ set => this[(TKey)key] = (TValue)value;
+ }
+
+ public ICollection Keys => keys;
+
+ ICollection IDictionary.Keys => keys;
+
+ public ICollection Values => values;
+
+ ICollection IDictionary.Values => values;
+
+ private IEqualityComparer KeyComparer => EqualityComparer.Default;
+
+ private IEqualityComparer ValueComparer => EqualityComparer.Default;
+
+ public int Count => count;
+
+ public bool IsReadOnly => true;
+
+ bool IDictionary.IsFixedSize => true;
+
+ object ICollection.SyncRoot => this;
+
+ bool ICollection.IsSynchronized => false;
+
+ public void Add(TKey key, TValue value)
+ {
+ if (count < keys.Length)
+ {
+ keys[count] = key;
+ values[count] = value;
+ count += 1;
+ }
+ else
+ {
+ throw new InvalidOperationException($"ArrayDictionary is at capacity {keys.Length}");
+ }
+ }
+
+ public void Add(KeyValuePair item)
+ {
+ Add(item.Key, item.Value);
+ }
+
+ public void Clear()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public bool Contains(KeyValuePair item)
+ {
+ var keyComparer = KeyComparer;
+ var valueComparer = ValueComparer;
+ for (int i = 0; i < count; i++)
+ {
+ if (keyComparer.Equals(item.Key, keys[i]) && valueComparer.Equals(item.Value, values[i]))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public bool ContainsKey(TKey key)
+ {
+ var comparer = KeyComparer;
+ for (int i = 0; i < count; i++)
+ {
+ if (comparer.Equals(key, keys[i]))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public void CopyTo(KeyValuePair[] array, int arrayIndex)
+ {
+ for (int i = 0; i < count; i++)
+ {
+ array[arrayIndex + i] = new KeyValuePair(keys[i], values[i]);
+ }
+ }
+
+ void ICollection.CopyTo(Array array, int index)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IEnumerator> GetEnumerator()
+ {
+ return new Enumerator(this);
+ }
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ IDictionaryEnumerator IDictionary.GetEnumerator()
+ {
+ return new Enumerator(this, emitDictionaryEntries: true);
+ }
+
+ public bool Remove(TKey key)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public bool Remove(KeyValuePair item)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public bool TryGetValue(TKey key, out TValue value)
+ {
+ var comparer = KeyComparer;
+ for (int i = 0; i < count; i++)
+ {
+ if (comparer.Equals(key, keys[i]))
+ {
+ value = values[i];
+ return true;
+ }
+ }
+
+ value = default;
+ return false;
+ }
+
+ bool IDictionary.Contains(object key)
+ {
+ if (key is not TKey typedKey)
+ {
+ return false;
+ }
+
+ return ContainsKey(typedKey);
+ }
+
+ void IDictionary.Add(object key, object value)
+ {
+ if (key is TKey typedKey && value is TValue typedValue)
+ {
+ Add(typedKey, typedValue);
+ }
+
+ throw new NotSupportedException();
+ }
+
+ void IDictionary.Remove(object key)
+ {
+ throw new NotImplementedException();
+ }
+
+ private struct Enumerator : IEnumerator>, IDictionaryEnumerator
+ {
+ private readonly ArrayDictionary _dictionary;
+ private readonly bool _emitDictionaryEntries;
+ private int _position;
+
+ public Enumerator(ArrayDictionary dictionary, bool emitDictionaryEntries = false)
+ {
+ this._dictionary = dictionary;
+ this._position = -1;
+ this._emitDictionaryEntries = emitDictionaryEntries;
+ }
+
+ public KeyValuePair Current =>
+ new KeyValuePair(
+ _dictionary.keys[_position],
+ _dictionary.values[_position]);
+
+ private DictionaryEntry CurrentDictionaryEntry => new DictionaryEntry(_dictionary.keys[_position], _dictionary.values[_position]);
+
+ object IEnumerator.Current => _emitDictionaryEntries ? CurrentDictionaryEntry : Current;
+
+ object IDictionaryEnumerator.Key => _dictionary.keys[_position];
+
+ object IDictionaryEnumerator.Value => _dictionary.values[_position];
+
+ DictionaryEntry IDictionaryEnumerator.Entry => CurrentDictionaryEntry;
+
+ public void Dispose()
+ {
+ }
+
+ public bool MoveNext()
+ {
+ _position += 1;
+ return _position < _dictionary.Count;
+ }
+
+ public void Reset()
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Build/Instance/ProjectItemInstance.cs b/src/Build/Instance/ProjectItemInstance.cs
index 6200da27e43..f9dc6429ec1 100644
--- a/src/Build/Instance/ProjectItemInstance.cs
+++ b/src/Build/Instance/ProjectItemInstance.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft. All rights reserved.
+// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
@@ -28,7 +28,13 @@ namespace Microsoft.Build.Execution
/// and evaluation has already been performed, so it is unnecessary bulk.
///
[DebuggerDisplay("{ItemType}={EvaluatedInclude} #DirectMetadata={DirectMetadataCount})")]
- public class ProjectItemInstance : IItem, ITaskItem2, IMetadataTable, ITranslatable, IDeepCloneable
+ public class ProjectItemInstance :
+ IItem,
+ ITaskItem2,
+ IMetadataTable,
+ ITranslatable,
+ IDeepCloneable,
+ IMetadataContainer
{
///
/// The project instance to which this item belongs.
@@ -515,6 +521,8 @@ IDictionary ITaskItem2.CloneCustomMetadataEscaped()
return ((ITaskItem2)_taskItem).CloneCustomMetadataEscaped();
}
+ IEnumerable> IMetadataContainer.EnumerateMetadata() => _taskItem.EnumerateMetadata();
+
#region IMetadataTable Members
///
@@ -723,7 +731,11 @@ internal sealed class TaskItem :
#if FEATURE_APPDOMAIN
MarshalByRefObject,
#endif
- ITaskItem2, IItem, ITranslatable, IEquatable
+ ITaskItem2,
+ IItem,
+ ITranslatable,
+ IEquatable,
+ IMetadataContainer
{
///
/// The source file that defined this item.
@@ -1045,6 +1057,36 @@ internal int DirectMetadataCount
get { return (_directMetadata == null) ? 0 : _directMetadata.Count; }
}
+ ///
+ /// Efficient way to retrieve metadata used by packet serialization
+ /// and binary logger.
+ ///
+ public IEnumerable> EnumerateMetadata()
+ {
+ // If we have item definitions, call the expensive property that does the right thing.
+ // Otherwise use _directMetadata to avoid allocations caused by DeepClone().
+ var list = _itemDefinitions != null ? MetadataCollection : _directMetadata;
+ if (list != null)
+ {
+ return EnumerateMetadata(list);
+ }
+ else
+ {
+ return Array.Empty>();
+ }
+ }
+
+ private IEnumerable> EnumerateMetadata(CopyOnWritePropertyDictionary list)
+ {
+ foreach (var projectMetadataInstance in list)
+ {
+ if (projectMetadataInstance != null)
+ {
+ yield return new KeyValuePair(projectMetadataInstance.Name, projectMetadataInstance.EvaluatedValue);
+ }
+ }
+ }
+
///
/// Unordered collection of evaluated metadata on the item.
/// If there is no metadata, returns an empty collection.
diff --git a/src/Build/Logging/BinaryLogger/BinaryLogRecordKind.cs b/src/Build/Logging/BinaryLogger/BinaryLogRecordKind.cs
index d8c90800210..dcb22fc4979 100644
--- a/src/Build/Logging/BinaryLogger/BinaryLogRecordKind.cs
+++ b/src/Build/Logging/BinaryLogger/BinaryLogRecordKind.cs
@@ -27,5 +27,6 @@ internal enum BinaryLogRecordKind
PropertyInitialValueSet,
NameValueList,
String,
+ TaskParameter
}
}
diff --git a/src/Build/Logging/BinaryLogger/BinaryLogReplayEventSource.cs b/src/Build/Logging/BinaryLogger/BinaryLogReplayEventSource.cs
index c6456c1a759..e4dc9c80b9c 100644
--- a/src/Build/Logging/BinaryLogger/BinaryLogReplayEventSource.cs
+++ b/src/Build/Logging/BinaryLogger/BinaryLogReplayEventSource.cs
@@ -2,6 +2,7 @@
using System.IO;
using System.IO.Compression;
using System.Threading;
+using Microsoft.Build.BackEnd;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
@@ -14,6 +15,14 @@ namespace Microsoft.Build.Logging
/// The class is public so that we can call it from MSBuild.exe when replaying a log file.
public sealed class BinaryLogReplayEventSource : EventArgsDispatcher
{
+ /// Touches the static constructor
+ /// to ensure it initializes
+ /// and
+ static BinaryLogReplayEventSource()
+ {
+ _ = ItemGroupLoggingHelper.ItemGroupIncludeLogMessagePrefix;
+ }
+
///
/// Read the provided binary log file and raise corresponding events for each BuildEventArgs
///
diff --git a/src/Build/Logging/BinaryLogger/BinaryLogger.cs b/src/Build/Logging/BinaryLogger/BinaryLogger.cs
index f9e9cb0b295..5274f54ab3a 100644
--- a/src/Build/Logging/BinaryLogger/BinaryLogger.cs
+++ b/src/Build/Logging/BinaryLogger/BinaryLogger.cs
@@ -41,7 +41,9 @@ public sealed class BinaryLogger : ILogger
// * NameValueList - deduplicate arrays of name-value pairs such as properties, items and metadata
// in a separate record and refer to those records from regular records
// where a list used to be written in-place
- internal const int FileFormatVersion = 10;
+ // version 11:
+ // - new record kind: TaskParameterEventArgs
+ internal const int FileFormatVersion = 11;
private Stream stream;
private BinaryWriter binaryWriter;
diff --git a/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs b/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs
index bd9ae6e9481..f9af8eed299 100644
--- a/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs
+++ b/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs
@@ -1,11 +1,17 @@
-using System;
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
+using Microsoft.Build.BackEnd;
+using Microsoft.Build.Collections;
using Microsoft.Build.Framework;
using Microsoft.Build.Framework.Profiler;
+using Microsoft.Build.Shared;
namespace Microsoft.Build.Logging
{
@@ -29,6 +35,8 @@ public class BuildEventArgsReader : IDisposable
///
/// A list of dictionaries we've encountered so far. Dictionaries are referred to by their order in this list.
///
+ /// This is designed to not hold on to strings. We just store the string indices and
+ /// hydrate the dictionary on demand before returning.
private readonly List<(int keyIndex, int valueIndex)[]> nameValueListRecords = new List<(int, int)[]>();
///
@@ -146,6 +154,9 @@ public BuildEventArgs Read()
case BinaryLogRecordKind.TaskCommandLine:
result = ReadTaskCommandLineEventArgs();
break;
+ case BinaryLogRecordKind.TaskParameter:
+ result = ReadTaskParameterEventArgs();
+ break;
case BinaryLogRecordKind.ProjectEvaluationStarted:
result = ReadProjectEvaluationStartedEventArgs();
break;
@@ -215,14 +226,17 @@ private void ReadNameValueList()
{
var list = nameValueListRecords[id];
- var dictionary = new Dictionary(list.Length);
+ // We can't cache these as they would hold on to strings.
+ // This reader is designed to not hold onto strings,
+ // so that we can fit in a 32-bit process when reading huge binlogs
+ var dictionary = ArrayDictionary.Create(list.Length);
for (int i = 0; i < list.Length; i++)
{
string key = GetStringFromRecord(list[i].keyIndex);
string value = GetStringFromRecord(list[i].valueIndex);
if (key != null)
{
- dictionary[key] = value;
+ dictionary.Add(key, value);
}
}
@@ -596,6 +610,27 @@ private BuildEventArgs ReadTaskCommandLineEventArgs()
return e;
}
+ private BuildEventArgs ReadTaskParameterEventArgs()
+ {
+ var fields = ReadBuildEventArgsFields();
+ // Read unused Importance, it defaults to Low
+ ReadInt32();
+
+ var kind = (TaskParameterMessageKind)ReadInt32();
+ var itemName = ReadDeduplicatedString();
+ var items = ReadTaskItemList() as IList;
+
+ var e = ItemGroupLoggingHelper.CreateTaskParameterEventArgs(
+ fields.BuildEventContext,
+ kind,
+ itemName,
+ items,
+ true,
+ fields.Timestamp);
+ e.ProjectFile = fields.ProjectFile;
+ return e;
+ }
+
private BuildEventArgs ReadCriticalBuildMessageEventArgs()
{
var fields = ReadBuildEventArgsFields();
@@ -896,65 +931,12 @@ private BuildEventContext ReadBuildEventContext()
return result;
}
- private class TaskItem : ITaskItem
- {
- private static readonly Dictionary emptyMetadata = new Dictionary();
-
- public string ItemSpec { get; set; }
- public IDictionary Metadata { get; }
-
- public TaskItem()
- {
- Metadata = new Dictionary();
- }
-
- public TaskItem(string itemSpec, IDictionary metadata)
- {
- ItemSpec = itemSpec;
- Metadata = metadata ?? emptyMetadata;
- }
-
- public int MetadataCount => Metadata.Count;
-
- public ICollection MetadataNames => (ICollection)Metadata.Keys;
-
- public IDictionary CloneCustomMetadata()
- {
- return (IDictionary)Metadata;
- }
-
- public void CopyMetadataTo(ITaskItem destinationItem)
- {
- throw new NotImplementedException();
- }
-
- public string GetMetadata(string metadataName)
- {
- return Metadata[metadataName];
- }
-
- public void RemoveMetadata(string metadataName)
- {
- throw new NotImplementedException();
- }
-
- public void SetMetadata(string metadataName, string metadataValue)
- {
- throw new NotImplementedException();
- }
-
- public override string ToString()
- {
- return $"{ItemSpec} Metadata: {MetadataCount}";
- }
- }
-
private ITaskItem ReadTaskItem()
{
string itemSpec = ReadDeduplicatedString();
var metadata = ReadStringDictionary();
- var taskItem = new TaskItem(itemSpec, metadata);
+ var taskItem = new TaskItemData(itemSpec, metadata);
return taskItem;
}
@@ -1079,7 +1061,10 @@ private string GetStringFromRecord(int index)
private int ReadInt32()
{
- return Read7BitEncodedInt(binaryReader);
+ // on some platforms (net5) this method was added to BinaryReader
+ // but it's not available on others. Call our own extension method
+ // explicitly to avoid ambiguity.
+ return BinaryReaderExtensions.Read7BitEncodedInt(binaryReader);
}
private long ReadInt64()
@@ -1102,30 +1087,6 @@ private TimeSpan ReadTimeSpan()
return new TimeSpan(binaryReader.ReadInt64());
}
- private int Read7BitEncodedInt(BinaryReader reader)
- {
- // Read out an Int32 7 bits at a time. The high bit
- // of the byte when on means to continue reading more bytes.
- int count = 0;
- int shift = 0;
- byte b;
- do
- {
- // Check for a corrupted stream. Read a max of 5 bytes.
- // In a future version, add a DataFormatException.
- if (shift == 5 * 7) // 5 bytes max per Int32, shift += 7
- {
- throw new FormatException();
- }
-
- // ReadByte handles end of stream cases for us.
- b = reader.ReadByte();
- count |= (b & 0x7F) << shift;
- shift += 7;
- } while ((b & 0x80) != 0);
- return count;
- }
-
private ProfiledLocation ReadProfiledLocation()
{
var numberOfHits = ReadInt32();
diff --git a/src/Build/Logging/BinaryLogger/BuildEventArgsWriter.cs b/src/Build/Logging/BinaryLogger/BuildEventArgsWriter.cs
index 3b37d1404cf..1acc641650b 100644
--- a/src/Build/Logging/BinaryLogger/BuildEventArgsWriter.cs
+++ b/src/Build/Logging/BinaryLogger/BuildEventArgsWriter.cs
@@ -1,13 +1,19 @@
-using System;
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
+using Microsoft.Build.BackEnd.Logging;
using Microsoft.Build.Exceptions;
+using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Microsoft.Build.Framework.Profiler;
using Microsoft.Build.Internal;
+using Microsoft.Build.Shared;
namespace Microsoft.Build.Logging
{
@@ -128,71 +134,33 @@ public void Write(BuildEventArgs e)
private void WriteCore(BuildEventArgs e)
{
- // the cases are ordered by most used first for performance
- if (e is BuildMessageEventArgs)
- {
- Write((BuildMessageEventArgs)e);
- }
- else if (e is TaskStartedEventArgs)
- {
- Write((TaskStartedEventArgs)e);
- }
- else if (e is TaskFinishedEventArgs)
- {
- Write((TaskFinishedEventArgs)e);
- }
- else if (e is TargetStartedEventArgs)
- {
- Write((TargetStartedEventArgs)e);
- }
- else if (e is TargetFinishedEventArgs)
- {
- Write((TargetFinishedEventArgs)e);
- }
- else if (e is BuildErrorEventArgs)
- {
- Write((BuildErrorEventArgs)e);
- }
- else if (e is BuildWarningEventArgs)
- {
- Write((BuildWarningEventArgs)e);
- }
- else if (e is ProjectStartedEventArgs)
- {
- Write((ProjectStartedEventArgs)e);
- }
- else if (e is ProjectFinishedEventArgs)
- {
- Write((ProjectFinishedEventArgs)e);
- }
- else if (e is BuildStartedEventArgs)
- {
- Write((BuildStartedEventArgs)e);
- }
- else if (e is BuildFinishedEventArgs)
- {
- Write((BuildFinishedEventArgs)e);
- }
- else if (e is ProjectEvaluationStartedEventArgs)
- {
- Write((ProjectEvaluationStartedEventArgs)e);
- }
- else if (e is ProjectEvaluationFinishedEventArgs)
- {
- Write((ProjectEvaluationFinishedEventArgs)e);
- }
- else
- {
- // convert all unrecognized objects to message
- // and just preserve the message
- var buildMessageEventArgs = new BuildMessageEventArgs(
- e.Message,
- e.HelpKeyword,
- e.SenderName,
- MessageImportance.Normal,
- e.Timestamp);
- buildMessageEventArgs.BuildEventContext = e.BuildEventContext ?? BuildEventContext.Invalid;
- Write(buildMessageEventArgs);
+ switch (e)
+ {
+ case BuildMessageEventArgs buildMessage: Write(buildMessage); break;
+ case TaskStartedEventArgs taskStarted: Write(taskStarted); break;
+ case TaskFinishedEventArgs taskFinished: Write(taskFinished); break;
+ case TargetStartedEventArgs targetStarted: Write(targetStarted); break;
+ case TargetFinishedEventArgs targetFinished: Write(targetFinished); break;
+ case BuildErrorEventArgs buildError: Write(buildError); break;
+ case BuildWarningEventArgs buildWarning: Write(buildWarning); break;
+ case ProjectStartedEventArgs projectStarted: Write(projectStarted); break;
+ case ProjectFinishedEventArgs projectFinished: Write(projectFinished); break;
+ case BuildStartedEventArgs buildStarted: Write(buildStarted); break;
+ case BuildFinishedEventArgs buildFinished: Write(buildFinished); break;
+ case ProjectEvaluationStartedEventArgs projectEvaluationStarted: Write(projectEvaluationStarted); break;
+ case ProjectEvaluationFinishedEventArgs projectEvaluationFinished: Write(projectEvaluationFinished); break;
+ default:
+ // convert all unrecognized objects to message
+ // and just preserve the message
+ var buildMessageEventArgs = new BuildMessageEventArgs(
+ e.Message,
+ e.HelpKeyword,
+ e.SenderName,
+ MessageImportance.Normal,
+ e.Timestamp);
+ buildMessageEventArgs.BuildEventContext = e.BuildEventContext ?? BuildEventContext.Invalid;
+ Write(buildMessageEventArgs);
+ break;
}
}
@@ -389,51 +357,57 @@ private void Write(BuildWarningEventArgs e)
private void Write(BuildMessageEventArgs e)
{
- if (e is CriticalBuildMessageEventArgs)
+ if (e is TaskParameterEventArgs taskParameter)
{
- Write((CriticalBuildMessageEventArgs)e);
+ Write(taskParameter);
return;
}
- if (e is TaskCommandLineEventArgs)
+ if (e is CriticalBuildMessageEventArgs criticalBuildMessage)
{
- Write((TaskCommandLineEventArgs)e);
+ Write(criticalBuildMessage);
return;
}
- if (e is ProjectImportedEventArgs)
+ if (e is TaskCommandLineEventArgs taskCommandLine)
{
- Write((ProjectImportedEventArgs)e);
+ Write(taskCommandLine);
return;
}
- if (e is TargetSkippedEventArgs)
+ if (e is ProjectImportedEventArgs projectImported)
{
- Write((TargetSkippedEventArgs)e);
+ Write(projectImported);
return;
}
- if (e is PropertyReassignmentEventArgs)
+ if (e is TargetSkippedEventArgs targetSkipped)
{
- Write((PropertyReassignmentEventArgs)e);
+ Write(targetSkipped);
return;
}
- if (e is UninitializedPropertyReadEventArgs)
+ if (e is PropertyReassignmentEventArgs propertyReassignment)
{
- Write((UninitializedPropertyReadEventArgs)e);
+ Write(propertyReassignment);
return;
}
- if (e is EnvironmentVariableReadEventArgs)
+ if (e is UninitializedPropertyReadEventArgs uninitializedPropertyRead)
{
- Write((EnvironmentVariableReadEventArgs)e);
+ Write(uninitializedPropertyRead);
return;
}
- if (e is PropertyInitialValueSetEventArgs)
+ if (e is EnvironmentVariableReadEventArgs environmentVariableRead)
{
- Write((PropertyInitialValueSetEventArgs)e);
+ Write(environmentVariableRead);
+ return;
+ }
+
+ if (e is PropertyInitialValueSetEventArgs propertyInitialValueSet)
+ {
+ Write(propertyInitialValueSet);
return;
}
@@ -507,9 +481,18 @@ private void Write(TaskCommandLineEventArgs e)
WriteDeduplicatedString(e.TaskName);
}
- private void WriteBuildEventArgsFields(BuildEventArgs e)
+ private void Write(TaskParameterEventArgs e)
{
- var flags = GetBuildEventArgsFieldFlags(e);
+ Write(BinaryLogRecordKind.TaskParameter);
+ WriteMessageFields(e, writeMessage: false);
+ Write((int)e.Kind);
+ WriteDeduplicatedString(e.ItemType);
+ WriteTaskItemList(e.Items, e.LogItemMetadata);
+ }
+
+ private void WriteBuildEventArgsFields(BuildEventArgs e, bool writeMessage = true)
+ {
+ var flags = GetBuildEventArgsFieldFlags(e, writeMessage);
Write((int)flags);
WriteBaseFields(e, flags);
}
@@ -547,9 +530,9 @@ private void WriteBaseFields(BuildEventArgs e, BuildEventArgsFieldFlags flags)
}
}
- private void WriteMessageFields(BuildMessageEventArgs e)
+ private void WriteMessageFields(BuildMessageEventArgs e, bool writeMessage = true)
{
- var flags = GetBuildEventArgsFieldFlags(e);
+ var flags = GetBuildEventArgsFieldFlags(e, writeMessage);
flags = GetMessageFlags(e, flags);
Write((int)flags);
@@ -644,7 +627,7 @@ private static BuildEventArgsFieldFlags GetMessageFlags(BuildMessageEventArgs e,
return flags;
}
- private static BuildEventArgsFieldFlags GetBuildEventArgsFieldFlags(BuildEventArgs e)
+ private static BuildEventArgsFieldFlags GetBuildEventArgsFieldFlags(BuildEventArgs e, bool writeMessage = true)
{
var flags = BuildEventArgsFieldFlags.None;
if (e.BuildEventContext != null)
@@ -657,7 +640,7 @@ private static BuildEventArgsFieldFlags GetBuildEventArgsFieldFlags(BuildEventAr
flags |= BuildEventArgsFieldFlags.HelpHeyword;
}
- if (!string.IsNullOrEmpty(e.Message))
+ if (writeMessage)
{
flags |= BuildEventArgsFieldFlags.Message;
}
@@ -681,21 +664,68 @@ private static BuildEventArgsFieldFlags GetBuildEventArgsFieldFlags(BuildEventAr
return flags;
}
- private void WriteTaskItemList(IEnumerable items)
+ private readonly List