1 // Copyright (c) Microsoft. All rights reserved. 2 // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 4 using System; 5 using System.Collections; 6 using System.Collections.Concurrent; 7 using System.Linq; 8 using System.Xml.Linq; 9 using Microsoft.Build.Framework; 10 11 namespace Microsoft.Build.Logging.StructuredLogger 12 { 13 /// <summary> 14 /// Class representation of an MSBuild project execution. 15 /// </summary> 16 internal class Project : LogProcessNode 17 { 18 /// <summary> 19 /// The full path to the MSBuild project file for this project. 20 /// </summary> 21 private string _projectFile; 22 23 /// <summary> 24 /// A lookup table mapping of target names to targets. 25 /// Target names are unique to a project and the id is not always specified in the log. 26 /// </summary> 27 private readonly ConcurrentDictionary<string, Target> _targetNameToTargetMap = new ConcurrentDictionary<string, Target>(StringComparer.OrdinalIgnoreCase); 28 29 /// <summary> 30 /// Initializes a new instance of the <see cref="Project"/> class. 31 /// </summary> 32 /// <param name="projectId">The project identifier.</param> 33 /// <param name="e">The <see cref="ProjectStartedEventArgs"/> instance containing the event data.</param> 34 /// <param name="parentPropertyBag">The parent property bag (to check for inherited properties).</param> Project(int projectId, ProjectStartedEventArgs e, PropertyBag parentPropertyBag)35 public Project(int projectId, ProjectStartedEventArgs e, PropertyBag parentPropertyBag) 36 { 37 Properties = new PropertyBag(parentPropertyBag); 38 Id = projectId; 39 40 TryUpdate(e); 41 } 42 43 /// <summary> 44 /// Add the given project as a child to this node. 45 /// </summary> 46 /// <param name="childProject">The child project to add.</param> AddChildProject(Project childProject)47 public void AddChildProject(Project childProject) 48 { 49 AddChildNode(childProject); 50 } 51 52 /// <summary> 53 /// Adds a new target node to the project. 54 /// </summary> 55 /// <param name="targetStartedEventArgs">The <see cref="TargetStartedEventArgs"/> instance containing the event data.</param> AddTarget(TargetStartedEventArgs targetStartedEventArgs)56 public void AddTarget(TargetStartedEventArgs targetStartedEventArgs) 57 { 58 var target = GetOrAddTargetByName(targetStartedEventArgs.TargetName, targetStartedEventArgs); 59 60 if (!string.IsNullOrEmpty(targetStartedEventArgs.ParentTarget)) 61 { 62 var parentTarget = GetOrAddTargetByName(targetStartedEventArgs.ParentTarget); 63 parentTarget.AddChildTarget(target); 64 } 65 else 66 { 67 AddChildNode(target); 68 } 69 } 70 71 /// <summary> 72 /// Gets the child target by identifier. 73 /// </summary> 74 /// <remarks>Throws if the child target does not exist</remarks> 75 /// <param name="id">The target identifier.</param> 76 /// <returns>Target with the given ID</returns> GetTargetById(int id)77 public Target GetTargetById(int id) 78 { 79 return _targetNameToTargetMap.Values.First(t => t.Id == id); 80 } 81 82 /// <summary> 83 /// Writes the project and its children to XML XElement representation. 84 /// </summary> 85 /// <param name="parentElement">The parent element.</param> SaveToElement(XElement parentElement)86 public override void SaveToElement(XElement parentElement) 87 { 88 // We could be in a situation where we never saw a "Parent" Target. So it's now 89 // in our scope but not rooted. This can happen when targets fail to run. 90 // Let's just add them back. 91 foreach (var orphan in _targetNameToTargetMap.Values.Where(t => t.Id < 0)) 92 { 93 AddChildNode(orphan); 94 } 95 96 var element = new XElement("Project", 97 new XAttribute("Name", Name.Replace("\"", string.Empty)), 98 new XAttribute("StartTime", StartTime), 99 new XAttribute("EndTime", EndTime), 100 new XAttribute("ProjectFile", _projectFile)); 101 102 parentElement.Add(element); 103 104 WriteProperties(element); 105 WriteChildren<Message>(element, () => new XElement("ProjectMessageEvents")); 106 WriteChildren<Project>(element); 107 WriteChildren<Target>(element); 108 } 109 110 /// <summary> 111 /// Try to update the project data given a project started event. This is useful if the project 112 /// was created (e.g. as a parent) before we saw the started event. 113 /// <remarks>Does nothing if the data has already been set or the new data is null.</remarks> 114 /// </summary> 115 /// <param name="projectStartedEventArgs">The <see cref="ProjectStartedEventArgs"/> instance containing the event data.</param> TryUpdate(ProjectStartedEventArgs projectStartedEventArgs)116 public void TryUpdate(ProjectStartedEventArgs projectStartedEventArgs) 117 { 118 if (Name == null && projectStartedEventArgs != null) 119 { 120 StartTime = projectStartedEventArgs.Timestamp; 121 Name = projectStartedEventArgs.Message; 122 _projectFile = projectStartedEventArgs.ProjectFile; 123 124 if (projectStartedEventArgs.GlobalProperties != null) 125 { 126 Properties.AddProperties(projectStartedEventArgs.GlobalProperties); 127 } 128 if (projectStartedEventArgs.Properties != null) 129 { 130 Properties.AddProperties(projectStartedEventArgs.Properties.Cast<DictionaryEntry>()); 131 } 132 } 133 } 134 135 /// <summary> 136 /// Returns a <see cref="System.String" /> that represents this instance. 137 /// </summary> 138 /// <returns> 139 /// A <see cref="System.String" /> that represents this instance. 140 /// </returns> ToString()141 public override string ToString() 142 { 143 return string.Format("{0} - {1}", Id, Name); 144 } 145 146 /// <summary> 147 /// Gets a child target by name. If the target doesn't exist a stub will be created. 148 /// </summary> 149 /// <param name="targetName">Name of the target.</param> 150 /// <param name="e">The <see cref="TargetStartedEventArgs"/> instance containing the event data, if any.</param> 151 /// <returns>Target node</returns> GetOrAddTargetByName(string targetName, TargetStartedEventArgs e = null)152 private Target GetOrAddTargetByName(string targetName, TargetStartedEventArgs e = null) 153 { 154 Target result = _targetNameToTargetMap.GetOrAdd(targetName, key=> new Target(key, e)); 155 156 if (e != null) 157 { 158 result.TryUpdate(e); 159 } 160 161 return result; 162 } 163 } 164 } 165