diff --git a/src/Build.UnitTests/BackEnd/NodePackets_Tests.cs b/src/Build.UnitTests/BackEnd/NodePackets_Tests.cs index 2d9699376c1..6b401f38f42 100644 --- a/src/Build.UnitTests/BackEnd/NodePackets_Tests.cs +++ b/src/Build.UnitTests/BackEnd/NodePackets_Tests.cs @@ -67,6 +67,7 @@ public void VerifyEventType() ExtendedBuildMessageEventArgs extMessage = new("extMsg", "SubCategoryForSchemaValidationErrors", "MSB4000", "file", 1, 2, 3, 4, "message", "help", "sender", MessageImportance.Normal); ExtendedCustomBuildEventArgs extCustom = new("extCustom", "message", "help", "sender"); CriticalBuildMessageEventArgs criticalMessage = new("Subcategory", "Code", "File", 1, 2, 3, 4, "{0}", "HelpKeyword", "Sender", DateTime.Now, "arg1"); + ExtendedCriticalBuildMessageEventArgs extCriticalMessage = new("extCritMsg", "Subcategory", "Code", "File", 1, 2, 3, 4, "{0}", "HelpKeyword", "Sender", DateTime.Now, "arg1"); PropertyInitialValueSetEventArgs propInit = new("prop", "val", "propsource", "message", "help", "sender", MessageImportance.Normal); MetaprojectGeneratedEventArgs metaProjectGenerated = new("metaName", "path", "message"); PropertyReassignmentEventArgs propReassign = new("prop", "prevValue", "newValue", "loc", "message", "help", "sender", MessageImportance.Normal); @@ -98,6 +99,7 @@ public void VerifyEventType() VerifyLoggingPacket(extMessage, LoggingEventType.ExtendedBuildMessageEvent); VerifyLoggingPacket(extCustom, LoggingEventType.ExtendedCustomEvent); VerifyLoggingPacket(criticalMessage, LoggingEventType.CriticalBuildMessage); + VerifyLoggingPacket(extCriticalMessage, LoggingEventType.ExtendedCriticalBuildMessageEvent); VerifyLoggingPacket(propInit, LoggingEventType.PropertyInitialValueSet); VerifyLoggingPacket(metaProjectGenerated, LoggingEventType.MetaprojectGenerated); VerifyLoggingPacket(propReassign, LoggingEventType.PropertyReassignment); @@ -299,6 +301,12 @@ public void TestTranslation() ExtendedMetadata = new Dictionary { { "m1", "v1" }, { "m2", "v2" } }, BuildEventContext = new BuildEventContext(1, 2, 3, 4, 5, 6, 7) }, + new ExtendedCriticalBuildMessageEventArgs("extCritMsg", "Subcategory", "Code", "File", 1, 2, 3, 4, "{0}", "HelpKeyword", "Sender", DateTime.Now, "arg1") + { + ExtendedData = "{'long-json':'mostly-strings'}", + ExtendedMetadata = new Dictionary { { "m1", "v1" }, { "m2", "v2" } }, + BuildEventContext = new BuildEventContext(1, 2, 3, 4, 5, 6, 7) + }, }; foreach (BuildEventArgs arg in testArgs) { diff --git a/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs b/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs index 6e385896a9c..4712e89e73d 100644 --- a/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs +++ b/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs @@ -574,6 +574,48 @@ public void RoundtripCriticalBuildMessageEventArgs() e => e.Subcategory); } + [Theory] + [InlineData(true)] + [InlineData(false)] + public void RoundtripExtendedCriticalBuildMessageEventArgs(bool withOptionalData) + { + var args = new ExtendedCriticalBuildMessageEventArgs( + "extCrit", + "Subcategory", + "Code", + "File", + 1, + 2, + 3, + 4, + "Message", + "Help", + "SenderName", + DateTime.Parse("12/12/2015 06:11:56 PM"), + withOptionalData ? new object[] { "argument0" } : null) + { + ExtendedData = withOptionalData ? "{'long-json':'mostly-strings'}" : null, + ExtendedMetadata = withOptionalData ? new Dictionary { { "m1", "v1" }, { "m2", "v2" } } : null, + BuildEventContext = withOptionalData ? new BuildEventContext(1, 2, 3, 4, 5, 6, 7) : null, + }; + + + Roundtrip(args, + e => e.Code, + e => e.ColumnNumber.ToString(), + e => e.EndColumnNumber.ToString(), + e => e.EndLineNumber.ToString(), + e => e.File, + e => e.LineNumber.ToString(), + e => e.Message, + e => e.ProjectFile, + e => e.Subcategory, + e => e.ExtendedType, + e => TranslationHelpers.ToString(e.ExtendedMetadata), + e => e.ExtendedData, + e => string.Join(", ", e.RawArguments ?? Array.Empty())); + } + [Fact] public void RoundtripTaskCommandLineEventArgs() { diff --git a/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs b/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs index 55c330ccd27..6c5ccd717d6 100644 --- a/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs +++ b/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs @@ -798,21 +798,49 @@ private BuildEventArgs ReadCriticalBuildMessageEventArgs() { var fields = ReadBuildEventArgsFields(readImportance: true); - var e = new CriticalBuildMessageEventArgs( - fields.Subcategory, - fields.Code, - fields.File, - fields.LineNumber, - fields.ColumnNumber, - fields.EndLineNumber, - fields.EndColumnNumber, - fields.Message, - fields.HelpKeyword, - fields.SenderName, - fields.Timestamp, - fields.Arguments); + BuildEventArgs e; + if (fields.Extended == null) + { + e = new CriticalBuildMessageEventArgs( + fields.Subcategory, + fields.Code, + fields.File, + fields.LineNumber, + fields.ColumnNumber, + fields.EndLineNumber, + fields.EndColumnNumber, + fields.Message, + fields.HelpKeyword, + fields.SenderName, + fields.Timestamp, + fields.Arguments) + { + ProjectFile = fields.ProjectFile, + }; + } + else + { + e = new ExtendedCriticalBuildMessageEventArgs( + fields.Extended?.ExtendedType ?? string.Empty, + fields.Subcategory, + fields.Code, + fields.File, + fields.LineNumber, + fields.ColumnNumber, + fields.EndLineNumber, + fields.EndColumnNumber, + fields.Message, + fields.HelpKeyword, + fields.SenderName, + fields.Timestamp, + fields.Arguments) + { + ProjectFile = fields.ProjectFile, + ExtendedMetadata = fields.Extended?.ExtendedMetadata, + ExtendedData = fields.Extended?.ExtendedData, + }; + } e.BuildEventContext = fields.BuildEventContext; - e.ProjectFile = fields.ProjectFile; return e; } diff --git a/src/Framework.UnitTests/ExtendedBuildEventArgs_Tests.cs b/src/Framework.UnitTests/ExtendedBuildEventArgs_Tests.cs index d1bcb987a1f..6739c53bc98 100644 --- a/src/Framework.UnitTests/ExtendedBuildEventArgs_Tests.cs +++ b/src/Framework.UnitTests/ExtendedBuildEventArgs_Tests.cs @@ -156,6 +156,43 @@ public void ExtendedMessageEventArgs_SerializationDeserialization(bool withOptio argDeserialized.Should().BeEquivalentTo(arg); } + [InlineData(true)] + [InlineData(false)] + [Theory] + public void ExtendedCriticalMessageEventArgs_SerializationDeserialization(bool withOptionalData) + { + ExtendedCriticalBuildMessageEventArgs arg = new( + type: "TypeOfExtendedCustom", + subcategory: withOptionalData ? "sub-type" : null, + code: withOptionalData ? "a-code" : null, + file: withOptionalData ? ".\\dev\\my.csproj" : null, + lineNumber: withOptionalData ? 1 : default, + columnNumber: withOptionalData ? 2 : default, + endLineNumber: withOptionalData ? 3 : default, + endColumnNumber: withOptionalData ? 4 : default, + message: withOptionalData ? "a message with args {0} {1}" : null, + helpKeyword: withOptionalData ? "MSBT123" : null, + senderName: withOptionalData ? $"UnitTest {Guid.NewGuid()}" : null, + eventTimestamp: withOptionalData ? DateTime.Parse("3/1/2017 11:11:56 AM") : DateTime.Now, + messageArgs: withOptionalData ? new object[] { "arg0val", "arg1val" } : null) + { + ExtendedData = withOptionalData ? "{'long-json':'mostly-strings'}" : null, + ExtendedMetadata = withOptionalData ? new Dictionary { { "m1", "v1" }, { "m2", "v2" } } : null, + BuildEventContext = withOptionalData ? new BuildEventContext(1, 2, 3, 4, 5, 6, 7) : null, + }; + + using MemoryStream stream = new MemoryStream(); + using BinaryWriter bw = new BinaryWriter(stream); + arg.WriteToStream(bw); + + stream.Position = 0; + using BinaryReader br = new BinaryReader(stream); + ExtendedBuildMessageEventArgs argDeserialized = new(); + argDeserialized.CreateFromStream(br, 80); + + argDeserialized.Should().BeEquivalentTo(arg); + } + [Fact] public void ExtendedCustomBuildEventArgs_Ctors() { @@ -215,4 +252,14 @@ public void ExtendedBuildMessageEventArgs_Ctors() ea = new ExtendedBuildMessageEventArgs("type", null, null, null, 1, 2, 3, 4, null, null, null, default, DateTime.Now); ea = new ExtendedBuildMessageEventArgs("type", null, null, null, 1, 2, 3, 4, null, null, null, default, DateTime.Now, null); } + + [Fact] + public void ExtendedCriticalBuildMessageEventArgs_Ctors() + { + var ea = new ExtendedCriticalBuildMessageEventArgs(); + ea = new ExtendedCriticalBuildMessageEventArgs("type"); + ea = new ExtendedCriticalBuildMessageEventArgs("type", "Subcategory", "Code", "File", 1, 2, 3, 4, "Message", "HelpKeyword", "sender"); + ea = new ExtendedCriticalBuildMessageEventArgs("type", "Subcategory", "Code", "File", 1, 2, 3, 4, "Message", "HelpKeyword", "sender", DateTime.Now); + ea = new ExtendedCriticalBuildMessageEventArgs("type", "Subcategory", "Code", "File", 1, 2, 3, 4, "{0}", "HelpKeyword", "sender", DateTime.Now, "arg1", "arg2"); + } } diff --git a/src/Framework/ExtendedCriticalBuildMessageEventArgs.cs b/src/Framework/ExtendedCriticalBuildMessageEventArgs.cs new file mode 100644 index 00000000000..f4c38172a02 --- /dev/null +++ b/src/Framework/ExtendedCriticalBuildMessageEventArgs.cs @@ -0,0 +1,148 @@ +// 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.Generic; +using System.IO; +using Microsoft.Build.Shared; + +namespace Microsoft.Build.Framework; + +/// +/// Critical message events arguments including extended data for event enriching. +/// Extended data are implemented by +/// +public sealed class ExtendedCriticalBuildMessageEventArgs : CriticalBuildMessageEventArgs, IExtendedBuildEventArgs +{ + /// + public string ExtendedType { get; set; } + + /// + public IDictionary? ExtendedMetadata { get; set; } + + /// + public string? ExtendedData { get; set; } + + /// + /// This constructor allows all event data to be initialized + /// + /// Type of . + /// event subcategory + /// event code + /// file associated with the event + /// line number (0 if not applicable) + /// column number (0 if not applicable) + /// end line number (0 if not applicable) + /// end column number (0 if not applicable) + /// text message + /// help keyword + /// name of event sender + public ExtendedCriticalBuildMessageEventArgs( + string type, + string? subcategory, + string? code, + string? file, + int lineNumber, + int columnNumber, + int endLineNumber, + int endColumnNumber, + string? message, + string? helpKeyword, + string? senderName) + : this(type, subcategory, code, file, lineNumber, columnNumber, endLineNumber, endColumnNumber, message, helpKeyword, senderName, DateTime.UtcNow) + { + // do nothing + } + + /// + /// This constructor allows timestamp to be set + /// + /// Type of . + /// event subcategory + /// event code + /// file associated with the event + /// line number (0 if not applicable) + /// column number (0 if not applicable) + /// end line number (0 if not applicable) + /// end column number (0 if not applicable) + /// text message + /// help keyword + /// name of event sender + /// custom timestamp for the event + public ExtendedCriticalBuildMessageEventArgs( + string type, + string? subcategory, + string? code, + string? file, + int lineNumber, + int columnNumber, + int endLineNumber, + int endColumnNumber, + string? message, + string? helpKeyword, + string? senderName, + DateTime eventTimestamp) + : this(type, subcategory, code, file, lineNumber, columnNumber, endLineNumber, endColumnNumber, message, helpKeyword, senderName, eventTimestamp, null!) + { + // do nothing + } + + /// + /// This constructor allows timestamp to be set + /// + /// Type of . + /// event subcategory + /// event code + /// file associated with the event + /// line number (0 if not applicable) + /// column number (0 if not applicable) + /// end line number (0 if not applicable) + /// end column number (0 if not applicable) + /// text message + /// help keyword + /// name of event sender + /// custom timestamp for the event + /// message arguments + public ExtendedCriticalBuildMessageEventArgs( + string type, + string? subcategory, + string? code, + string? file, + int lineNumber, + int columnNumber, + int endLineNumber, + int endColumnNumber, + string? message, + string? helpKeyword, + string? senderName, + DateTime eventTimestamp, + params object[]? messageArgs) + //// Force importance to High. + : base(subcategory, code, file, lineNumber, columnNumber, endLineNumber, endColumnNumber, message, helpKeyword, senderName, eventTimestamp, messageArgs) => ExtendedType = type; + + /// + /// Default constructor. Used for deserialization. + /// + internal ExtendedCriticalBuildMessageEventArgs() : this("undefined") + { + // do nothing + } + + /// + /// This constructor specifies only type of extended data. + /// + /// Type of . + public ExtendedCriticalBuildMessageEventArgs(string type) => ExtendedType = type; + + internal override void WriteToStream(BinaryWriter writer) + { + base.WriteToStream(writer); + writer.WriteExtendedBuildEventData(this); + } + + internal override void CreateFromStream(BinaryReader reader, int version) + { + base.CreateFromStream(reader, version); + reader.ReadExtendedBuildEventData(this); + } +} diff --git a/src/Shared/LogMessagePacketBase.cs b/src/Shared/LogMessagePacketBase.cs index 4cfbf546ce0..474de6dc063 100644 --- a/src/Shared/LogMessagePacketBase.cs +++ b/src/Shared/LogMessagePacketBase.cs @@ -199,7 +199,12 @@ internal enum LoggingEventType : int /// /// Event is /// - UninitializedPropertyRead = 32 + UninitializedPropertyRead = 32, + + /// + /// Event is + /// + ExtendedCriticalBuildMessageEvent = 33, } #endregion @@ -597,6 +602,7 @@ private BuildEventArgs GetBuildEventArgFromId() LoggingEventType.ExtendedBuildErrorEvent => new ExtendedBuildErrorEventArgs(), LoggingEventType.ExtendedBuildWarningEvent => new ExtendedBuildWarningEventArgs(), LoggingEventType.ExtendedBuildMessageEvent => new ExtendedBuildMessageEventArgs(), + LoggingEventType.ExtendedCriticalBuildMessageEvent => new ExtendedCriticalBuildMessageEventArgs(), LoggingEventType.ExternalProjectStartedEvent => new ExternalProjectStartedEventArgs(null, null, null, null, null), LoggingEventType.ExternalProjectFinishedEvent => new ExternalProjectFinishedEventArgs(null, null, null, null, false), LoggingEventType.CriticalBuildMessage => new CriticalBuildMessageEventArgs(null, null, null, -1, -1, -1, -1, null, null, null), @@ -695,6 +701,10 @@ private LoggingEventType GetLoggingEventId(BuildEventArgs eventArg) { return LoggingEventType.CriticalBuildMessage; } + else if (eventType == typeof(ExtendedCriticalBuildMessageEventArgs)) + { + return LoggingEventType.ExtendedCriticalBuildMessageEvent; + } else if (eventType == typeof(MetaprojectGeneratedEventArgs)) { return LoggingEventType.MetaprojectGenerated;