File: ObjectModel\Project.cs
Web Access
Project: ..\..\..\src\Samples\XmlFileLogger\XmlFileLogger.csproj (XmlFileLogger)
// 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.Concurrent;
using System.Linq;
using System.Xml.Linq;
using Microsoft.Build.Framework;
 
#nullable disable
 
namespace Microsoft.Build.Logging.StructuredLogger
{
    /// <summary>
    /// Class representation of an MSBuild project execution.
    /// </summary>
    internal sealed class Project : LogProcessNode
    {
        /// <summary>
        /// The full path to the MSBuild project file for this project.
        /// </summary>
        private string _projectFile;
 
        /// <summary>
        /// A lookup table mapping of target names to targets.
        /// Target names are unique to a project and the id is not always specified in the log.
        /// </summary>
        private readonly ConcurrentDictionary<string, Target> _targetNameToTargetMap = new ConcurrentDictionary<string, Target>(StringComparer.OrdinalIgnoreCase);
 
        /// <summary>
        /// Initializes a new instance of the <see cref="Project"/> class.
        /// </summary>
        /// <param name="projectId">The project identifier.</param>
        /// <param name="e">The <see cref="ProjectStartedEventArgs"/> instance containing the event data.</param>
        /// <param name="parentPropertyBag">The parent property bag (to check for inherited properties).</param>
        public Project(int projectId, ProjectStartedEventArgs e, PropertyBag parentPropertyBag)
        {
            Properties = new PropertyBag(parentPropertyBag);
            Id = projectId;
 
            TryUpdate(e);
        }
 
        /// <summary>
        /// Add the given project as a child to this node.
        /// </summary>
        /// <param name="childProject">The child project to add.</param>
        public void AddChildProject(Project childProject)
        {
            AddChildNode(childProject);
        }
 
        /// <summary>
        /// Adds a new target node to the project.
        /// </summary>
        /// <param name="targetStartedEventArgs">The <see cref="TargetStartedEventArgs"/> instance containing the event data.</param>
        public void AddTarget(TargetStartedEventArgs targetStartedEventArgs)
        {
            var target = GetOrAddTargetByName(targetStartedEventArgs.TargetName, targetStartedEventArgs);
 
            if (!string.IsNullOrEmpty(targetStartedEventArgs.ParentTarget))
            {
                var parentTarget = GetOrAddTargetByName(targetStartedEventArgs.ParentTarget);
                parentTarget.AddChildTarget(target);
            }
            else
            {
                AddChildNode(target);
            }
        }
 
        /// <summary>
        /// Gets the child target by identifier.
        /// </summary>
        /// <remarks>Throws if the child target does not exist</remarks>
        /// <param name="id">The target identifier.</param>
        /// <returns>Target with the given ID</returns>
        public Target GetTargetById(int id)
        {
            return _targetNameToTargetMap.Values.First(t => t.Id == id);
        }
 
        /// <summary>
        /// Writes the project and its children to XML XElement representation.
        /// </summary>
        /// <param name="parentElement">The parent element.</param>
        public override void SaveToElement(XElement parentElement)
        {
            // We could be in a situation where we never saw a "Parent" Target. So it's now
            // in our scope but not rooted. This can happen when targets fail to run.
            // Let's just add them back.
            foreach (var orphan in _targetNameToTargetMap.Values.Where(t => t.Id < 0))
            {
                AddChildNode(orphan);
            }
 
            var element = new XElement("Project",
                new XAttribute("Name", Name.Replace("\"", string.Empty)),
                new XAttribute("StartTime", StartTime),
                new XAttribute("EndTime", EndTime),
                new XAttribute("ProjectFile", _projectFile));
 
            parentElement.Add(element);
 
            WriteProperties(element);
            WriteChildren<Message>(element, () => new XElement("ProjectMessageEvents"));
            WriteChildren<Project>(element);
            WriteChildren<Target>(element);
        }
 
        /// <summary>
        /// Try to update the project data given a project started event. This is useful if the project
        /// was created (e.g. as a parent) before we saw the started event.
        /// <remarks>Does nothing if the data has already been set or the new data is null.</remarks>
        /// </summary>
        /// <param name="projectStartedEventArgs">The <see cref="ProjectStartedEventArgs"/> instance containing the event data.</param>
        public void TryUpdate(ProjectStartedEventArgs projectStartedEventArgs)
        {
            if (Name == null && projectStartedEventArgs != null)
            {
                StartTime = projectStartedEventArgs.Timestamp;
                Name = projectStartedEventArgs.Message;
                _projectFile = projectStartedEventArgs.ProjectFile;
 
                if (projectStartedEventArgs.GlobalProperties != null)
                {
                    Properties.AddProperties(projectStartedEventArgs.GlobalProperties);
                }
                if (projectStartedEventArgs.Properties != null)
                {
                    Properties.AddProperties(projectStartedEventArgs.Properties.Cast<DictionaryEntry>());
                }
            }
        }
 
        /// <summary>
        /// Returns a <see cref="System.String" /> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String" /> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return string.Format("{0} - {1}", Id, Name);
        }
 
        /// <summary>
        /// Gets a child target by name. If the target doesn't exist a stub will be created.
        /// </summary>
        /// <param name="targetName">Name of the target.</param>
        /// <param name="e">The <see cref="TargetStartedEventArgs"/> instance containing the event data, if any.</param>
        /// <returns>Target node</returns>
        private Target GetOrAddTargetByName(string targetName, TargetStartedEventArgs e = null)
        {
            Target result = _targetNameToTargetMap.GetOrAdd(targetName, key => new Target(key, e));
 
            if (e != null)
            {
                result.TryUpdate(e);
            }
 
            return result;
        }
    }
}