| File: BuildEventArgs.cs | Web Access |
| Project: src\msbuild\src\Framework\Microsoft.Build.Framework.csproj (Microsoft.Build.Framework) |
// 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.IO; using System.Runtime.Serialization; using System.Text; namespace Microsoft.Build.Framework { /// <summary> /// This class encapsulates the default data associated with build events. /// It is intended to be extended/sub-classed. /// </summary> // WARNING: marking a type [Serializable] without implementing // ISerializable imposes a serialization contract -- it is a // promise to never change the type's fields i.e. the type is // immutable; adding new fields in the next version of the type // without following certain special FX guidelines, can break both // forward and backward compatibility [Serializable] public abstract class BuildEventArgs : EventArgs { /// <summary> /// Message. Volatile because it may be updated lock-free after construction. /// </summary> private volatile string? message; /// <summary> /// Help keyword /// </summary> private string? helpKeyword; /// <summary> /// Sender name /// </summary> private string? senderName; /// <summary> /// Timestamp /// </summary> private DateTime timestamp; [NonSerialized] private DateTime? _localTimestamp; /// <summary> /// Thread id /// </summary> private int threadId; /// <summary> /// Build event context /// </summary> [OptionalField(VersionAdded = 2)] private BuildEventContext? buildEventContext; /// <summary> /// Default constructor /// </summary> protected BuildEventArgs() : this(null, null, null, DateTime.UtcNow) { } /// <summary> /// This constructor allows all event data to be initialized /// </summary> /// <param name="message">text message</param> /// <param name="helpKeyword">help keyword </param> /// <param name="senderName">name of event sender</param> protected BuildEventArgs(string? message, string? helpKeyword, string? senderName) : this(message, helpKeyword, senderName, DateTime.UtcNow) { } /// <summary> /// This constructor allows all event data to be initialized while providing a custom timestamp. /// </summary> /// <param name="message">text message</param> /// <param name="helpKeyword">help keyword </param> /// <param name="senderName">name of event sender</param> /// <param name="eventTimestamp">TimeStamp of when the event was created</param> protected BuildEventArgs(string? message, string? helpKeyword, string? senderName, DateTime eventTimestamp) { this.message = message; this.helpKeyword = helpKeyword; this.senderName = senderName; timestamp = eventTimestamp; threadId = System.Threading.Thread.CurrentThread.GetHashCode(); } /// <summary> /// The time when event was raised. /// </summary> public DateTime Timestamp { get { // Rather than storing dates in Local time all the time, we store in UTC type, and only // convert to Local when the user requests access to this field. This lets us avoid the // expensive conversion to Local time unless it's absolutely necessary. if (!_localTimestamp.HasValue) { _localTimestamp = timestamp.Kind == DateTimeKind.Utc || timestamp.Kind == DateTimeKind.Unspecified ? timestamp.ToLocalTime() : timestamp; } return _localTimestamp.Value; } } /// <summary> /// Exposes the private timestamp field to derived types. /// Used for serialization. Avoids the side effects of calling the /// <see cref="Timestamp"/> getter. /// </summary> protected internal DateTime RawTimestamp { get => timestamp; set => timestamp = value; } /// <summary> /// The thread that raised event. /// </summary> public int ThreadId => threadId; /// <summary> /// Text of event. /// </summary> public virtual string? Message { get => message; protected set => message = value; } /// <summary> /// Exposes the underlying message field without side-effects. /// Used for serialization. /// </summary> protected internal string? RawMessage { get => FormattedMessage; set => message = value; } /// <summary> /// Like <see cref="RawMessage"/> but returns a formatted message string if available. /// Used for serialization. /// </summary> private protected virtual string? FormattedMessage { get => message; } /// <summary> /// Custom help keyword associated with event. /// </summary> public string? HelpKeyword => helpKeyword; /// <summary> /// Name of the object sending this event. /// </summary> public string? SenderName => senderName; /// <summary> /// Event contextual information for the build event argument /// </summary> public BuildEventContext? BuildEventContext { get => buildEventContext; set => buildEventContext = value; } #region CustomSerializationToStream /// <summary> /// Serializes to a stream through a binary writer /// </summary> /// <param name="writer">Binary writer which is attached to the stream the event will be serialized into</param> /// <param name="messageToWrite">The message to write to the stream.</param> private protected void WriteToStreamWithExplicitMessage(BinaryWriter writer, string? messageToWrite) { writer.WriteOptionalString(messageToWrite); writer.WriteOptionalString(helpKeyword); writer.WriteOptionalString(senderName); writer.WriteTimestamp(timestamp); writer.Write(threadId); writer.WriteOptionalBuildEventContext(buildEventContext); } /// <summary> /// Serializes to a stream through a binary writer /// </summary> /// <param name="writer">Binary writer which is attached to the stream the event will be serialized into</param> internal virtual void WriteToStream(BinaryWriter writer) { WriteToStreamWithExplicitMessage(writer, RawMessage); } /// <summary> /// Deserializes from a stream through a binary reader /// </summary> /// <param name="reader">Binary reader which is attached to the stream the event will be deserialized from</param> /// <param name="version">The version of the runtime the message packet was created from</param> internal virtual void CreateFromStream(BinaryReader reader, int version) { message = reader.ReadOptionalString(); helpKeyword = reader.ReadOptionalString(); senderName = reader.ReadOptionalString(); long timestampTicks = reader.ReadInt64(); if (version > 20) { DateTimeKind kind = (DateTimeKind)reader.ReadInt32(); timestamp = new DateTime(timestampTicks, kind); } else { timestamp = new DateTime(timestampTicks); } threadId = reader.ReadInt32(); if (reader.ReadByte() == 0) { buildEventContext = null; } else { int nodeId = reader.ReadInt32(); int projectContextId = reader.ReadInt32(); int targetId = reader.ReadInt32(); int taskId = reader.ReadInt32(); if (version > 20) { int submissionId = reader.ReadInt32(); int projectInstanceId = reader.ReadInt32(); int evaluationId = reader.ReadInt32(); buildEventContext = new BuildEventContext(submissionId, nodeId, evaluationId, projectInstanceId, projectContextId, targetId, taskId); } else { buildEventContext = new BuildEventContext(nodeId, targetId, projectContextId, taskId); } } } #endregion #region SetSerializationDefaults /// <summary> /// Run before the object has been deserialized /// UNDONE (Logging.) Can this and the next function go away, and instead return a BuildEventContext.Invalid from /// the property if the buildEventContext field is null? /// </summary> [OnDeserializing] private void SetBuildEventContextDefaultBeforeSerialization(StreamingContext sc) { // Don't want to create a new one here as default all the time as that would be a lot of // possibly useless allocations buildEventContext = null; } /// <summary> /// Run after the object has been deserialized /// </summary> [OnDeserialized] private void SetBuildEventContextDefaultAfterSerialization(StreamingContext sc) { if (buildEventContext == null) { buildEventContext = BuildEventContext.Invalid; } } #endregion /// <summary> /// This is the default stub implementation, only here as a safeguard. /// Actual logic is injected from Microsoft.Build.dll to replace this. /// This is used by the Message property overrides to reconstruct the /// message lazily on demand. /// </summary> internal static Func<string, string?[], string> ResourceStringFormatter = (resourceName, arguments) => { var sb = new StringBuilder(); sb.Append(resourceName); sb.Append('('); bool notFirst = false; foreach (var argument in arguments) { if (notFirst) { sb.Append(','); } else { notFirst = true; } sb.Append(argument); } sb.Append(')'); return sb.ToString(); }; /// <summary> /// Shortcut method to mimic the original logic of creating the formatted strings. /// </summary> /// <param name="resourceName">Name of the resource string.</param> /// <param name="arguments">Optional list of arguments to pass to the formatted string.</param> /// <returns>The concatenated formatted string.</returns> internal static string FormatResourceStringIgnoreCodeAndKeyword(string resourceName, params string?[] arguments) { return ResourceStringFormatter(resourceName, arguments); } } }