src/StructuredLogger/Construction/Construction.cs (988 lines of code) (raw):

using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; using Microsoft.Build.Collections; using Microsoft.Build.Framework; using Microsoft.Build.Framework.Profiler; namespace Microsoft.Build.Logging.StructuredLogger { /// <summary> /// Constructs an Object Model graph from MSBuild event arguments /// </summary> public class Construction { public Build Build { get; private set; } private readonly ConcurrentDictionary<int, Project> _projectIdToProjectMap = new ConcurrentDictionary<int, Project>(); private readonly Dictionary<string, string> _taskToAssemblyMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); private readonly object syncLock = new object(); private readonly MessageProcessor messageProcessor; private readonly StringCache stringTable; private System.Threading.Tasks.Task bgWorker; private BlockingCollection<System.Threading.Tasks.Task> bgJobPool; public StringCache StringTable => stringTable; public NamedNode EvaluationFolder => Build.EvaluationFolder; public NamedNode EnvironmentFolder => Build.EnvironmentFolder; public Construction() { Build = new Build(); Build.Name = "Build"; this.stringTable = Build.StringTable; this.messageProcessor = new MessageProcessor(this, stringTable); Intern(Strings.Assembly); Intern(Strings.CommandLineArguments); Intern(Strings.DoubleWrites); Intern(Strings.Evaluation); Intern(Strings.Note); Intern(Strings.OutputItems); Intern(Strings.Parameters); Intern(Strings.Properties); Intern(Strings.UnusedLocations); Intern(Strings.Warnings); Intern(nameof(AddItem)); Intern(nameof(CopyTask)); Intern(nameof(CscTask)); Intern(nameof(ResolveAssemblyReferenceTask)); Intern(nameof(EntryTarget)); Intern(nameof(Folder)); Intern(nameof(Import)); Intern(nameof(Item)); Intern(nameof(Metadata)); Intern(nameof(NoImport)); Intern(nameof(Parameter)); Intern(nameof(ProjectEvaluation)); Intern(nameof(Property)); Intern(nameof(RemoveItem)); Intern(nameof(Target)); Intern(nameof(Task)); Intern(nameof(TimedNode)); bgJobPool = new(2000); bgWorker = new System.Threading.Tasks.Task(() => { System.Threading.Tasks.Parallel.ForEach(bgJobPool.GetConsumingEnumerable(), task => { task.Start(); task.Wait(); }); }, System.Threading.Tasks.TaskCreationOptions.LongRunning); bgWorker.Start(); } public void Shutdown() { bgJobPool.CompleteAdding(); bgWorker.Wait(); } private string Intern(string text) => stringTable.Intern(text); private string SoftIntern(string text) => stringTable.SoftIntern(text); private readonly HashSet<string> environmentVariablesUsed = new HashSet<string>(StringComparer.OrdinalIgnoreCase); public void AddEnvironmentVariable(string environmentVariableName, string environmentVariableValue) { if (environmentVariablesUsed.Add(environmentVariableName)) { var property = new Property { Name = environmentVariableName, Value = environmentVariableValue }; EnvironmentFolder.AddChild(property); } } public void BuildStarted(object sender, BuildStartedEventArgs args) { try { lock (syncLock) { Build.StartTime = args.Timestamp; if (args.BuildEnvironment?.Count > 0) { AddProperties(EnvironmentFolder, args.BuildEnvironment); } // realize the evaluation folder now so it is ordered before the main solution node _ = EvaluationFolder; } } catch (Exception ex) { HandleException(ex); } } public void BuildFinished(object sender, BuildFinishedEventArgs args) { try { lock (syncLock) { Build.EndTime = args.Timestamp; Build.Succeeded = args.Succeeded; EnvironmentFolder.AddChild(new Note { Text = Intern(Strings.TruncatedEnvironment) }); if (messageProcessor.DetailedSummary.Length > 0) { var summary = Build.GetOrCreateNodeWithName<Message>(Intern(Strings.DetailedSummary)); if (messageProcessor.DetailedSummary[0] == '\n') { messageProcessor.DetailedSummary.Remove(0, 1); } string fullText = messageProcessor.DetailedSummary.ToString(); #if DEBUG fullText = Intern(fullText); #endif summary.Text = fullText; } //Build.VisitAllChildren<Project>(p => CalculateTargetGraph(p)); } } catch (Exception ex) { HandleException(ex); } } public void ProjectStarted(object sender, ProjectStartedEventArgs args) { try { lock (syncLock) { Project parentProject = null; TreeNode parentNode = null; var parentContext = args?.ParentProjectBuildEventContext; if (parentContext != null) { int parentProjectId = parentContext.ProjectContextId; if (parentProjectId > 0) { parentProject = GetOrAddProject(parentProjectId); } int parentTaskId = parentContext.TaskId; if (parentProject != null && parentTaskId > 0) { parentNode = parentProject.GetTaskById(parentTaskId); } } var project = GetOrAddProject(args, parentProject); // only parent the project if it's not already in the tree if (project.Parent == null) { parentNode = parentNode ?? parentProject; if (parentNode != null) { parentNode.AddChild(project); } else { // This is a "Root" project (no parent project). Build.AddChild(project); } } } } catch (Exception ex) { HandleException(ex); } } public void ProjectFinished(object sender, ProjectFinishedEventArgs args) { try { lock (syncLock) { var project = GetOrAddProject(args.BuildEventContext.ProjectContextId); project.EndTime = args.Timestamp; } } catch (Exception ex) { HandleException(ex); } } public void TargetStarted(object sender, TargetStartedEventArgs args) { AddTargetCore( args, Intern(args.TargetName), Intern(args.ParentTarget), Intern(args.TargetFile), args.BuildReason); } private Target AddTargetCore( BuildEventArgs args, string targetName, string parentTargetName, string targetFile, TargetBuiltReason targetBuiltReason) { try { lock (syncLock) { var project = GetOrAddProject(args.BuildEventContext.ProjectContextId); var target = project.CreateTarget(targetName, args.BuildEventContext.TargetId); target.NodeId = args.BuildEventContext.NodeId; target.StartTime = args.Timestamp; target.EndTime = target.StartTime; // will properly set later target.ParentTarget = parentTargetName; target.TargetBuiltReason = targetBuiltReason; target.SourceFilePath = targetFile; project.AddChild(target); return target; } } catch (Exception ex) { HandleException(ex); } return null; } public void TargetFinished(object sender, TargetFinishedEventArgs args) { try { lock (syncLock) { var project = GetOrAddProject(args.BuildEventContext.ProjectContextId); var target = project.GetTargetById(args.BuildEventContext.TargetId); target.EndTime = args.Timestamp; target.Succeeded = args.Succeeded; if (args.TargetOutputs != null) { var targetOutputsFolder = target.GetOrCreateNodeWithName<Folder>(Intern(Strings.TargetOutputs)); foreach (ITaskItem targetOutput in args.TargetOutputs) { var item = new Item(); item.Text = Intern(targetOutput.ItemSpec); foreach (DictionaryEntry metadata in targetOutput.CloneCustomMetadata()) { var metadataNode = new Metadata(); metadataNode.Name = Intern(Convert.ToString(metadata.Key)); metadataNode.Value = Intern(Convert.ToString(metadata.Value)); item.AddChild(metadataNode); } targetOutputsFolder.AddChild(item); } } } } catch (Exception ex) { HandleException(ex); } } public void TargetSkipped(TargetSkippedEventArgs2 args) { string targetName = Intern(args.TargetName); string messageText = args.Message; var originalBuildEventContext = args.OriginalBuildEventContext; var skipReason = args.SkipReason; if ((skipReason == TargetSkipReason.PreviouslyBuiltSuccessfully || skipReason == TargetSkipReason.PreviouslyBuiltUnsuccessfully) && originalBuildEventContext != null) { var prefix = "Target \"" + targetName + "\" "; // trim the Target Name text since the node will already display that if (messageText.StartsWith(prefix, StringComparison.Ordinal)) { messageText = messageText.Substring(prefix.Length); } } messageText = Intern(messageText); var target = AddTargetCore( args, targetName, Intern(args.ParentTarget), Intern(args.TargetFile), args.BuildReason); if (originalBuildEventContext != null && originalBuildEventContext.ProjectContextId != BuildEventContext.InvalidProjectContextId) { var originalProject = GetProject(originalBuildEventContext.ProjectContextId); if (originalProject != null) { target.ParentTarget = messageText; if (originalBuildEventContext.TargetId != -1 && originalProject.GetTargetById(originalBuildEventContext.TargetId) is Target originalTarget) { target.OriginalNode = originalTarget; } else { // the original target was skipped because of false condition, so its target id == -1 // Need to look it up by name, if unambiguous var candidates = originalProject .Children .OfType<Target>() .Where(t => t.Name == targetName) .ToArray(); if (candidates.Length == 1) { originalTarget = candidates[0]; } else { originalTarget = null; } target.OriginalNode = (TimedNode)originalTarget ?? originalProject; } } } else { var messageNode = new Message { Text = messageText }; target.AddChild(messageNode); } } public void TaskStarted(object sender, TaskStartedEventArgs args) { try { lock (syncLock) { Build.Statistics.Tasks++; var project = GetOrAddProject(args.BuildEventContext.ProjectContextId); var target = project.GetTargetById(args.BuildEventContext.TargetId); var task = CreateTask(args); target.AddChild(task); project.OnTaskAdded(task); if (args is TaskStartedEventArgs2 taskStarted2) { task.LineNumber = taskStarted2.LineNumber; } } } catch (Exception ex) { HandleException(ex); } } public void TaskFinished(object sender, TaskFinishedEventArgs args) { try { lock (syncLock) { var project = GetOrAddProject(args.BuildEventContext.ProjectContextId); var target = project.GetTargetById(args.BuildEventContext.TargetId); var task = target.GetTaskById(args.BuildEventContext.TaskId); task.EndTime = args.Timestamp; } } catch (Exception ex) { HandleException(ex); } } private bool sawCulture = false; public void MessageRaised(object sender, BuildMessageEventArgs args) { try { lock (syncLock) { if (args is TargetSkippedEventArgs2 targetSkipped) { TargetSkipped(targetSkipped); return; } if (!sawCulture && args.SenderName == "BinaryLogger" && args.Message is string message && message.StartsWith("CurrentUICulture", StringComparison.Ordinal)) { sawCulture = true; var kvp = TextUtilities.ParseNameValue(message); string culture = kvp.Value; if (!string.IsNullOrEmpty(culture)) { Strings.Initialize(culture); } } messageProcessor.Process(args); } } catch (Exception ex) { HandleException(ex); } } public void CustomEventRaised(object sender, CustomBuildEventArgs args) { try { lock (syncLock) { messageProcessor.Process(new BuildMessageEventArgs( Intern(args.Message), Intern(args.HelpKeyword), Intern(args.SenderName), MessageImportance.Low)); } } catch (Exception ex) { HandleException(ex); } } public void StatusEventRaised(object sender, BuildStatusEventArgs e) { try { lock (syncLock) { if (e is ProjectEvaluationStartedEventArgs projectEvaluationStarted) { var evaluationId = projectEvaluationStarted.BuildEventContext.EvaluationId; var projectFilePath = Intern(projectEvaluationStarted.ProjectFile); var projectName = Intern(Path.GetFileName(projectFilePath)); var nodeName = Intern(GetEvaluationProjectName(evaluationId, projectName)); var projectEvaluation = new ProjectEvaluation { Name = nodeName }; EvaluationFolder.AddChild(projectEvaluation); projectEvaluation.ProjectFile = projectFilePath; projectEvaluation.Id = evaluationId; projectEvaluation.EvaluationText = Intern("id:" + evaluationId); projectEvaluation.NodeId = e.BuildEventContext.NodeId; projectEvaluation.StartTime = e.Timestamp; projectEvaluation.EndTime = e.Timestamp; } else if (e is ProjectEvaluationFinishedEventArgs projectEvaluationFinished) { var evaluationId = projectEvaluationFinished.BuildEventContext.EvaluationId; var projectFilePath = Intern(projectEvaluationFinished.ProjectFile); var projectName = Intern(Path.GetFileName(projectFilePath)); var nodeName = Intern(GetEvaluationProjectName(evaluationId, projectName)); var projectEvaluation = EvaluationFolder.FindLastChild<ProjectEvaluation>(e => e.Id == evaluationId); if (projectEvaluation == null) { // no matching ProjectEvaluationStarted return; } projectEvaluation.EndTime = e.Timestamp; var profilerResult = projectEvaluationFinished.ProfilerResult; if (profilerResult != null && projectName != null) { ConstructProfilerResult(projectEvaluation, profilerResult.Value); } // Pre-create folder before starting the fill on the background thread. Folder globFolder = null; Folder itemsNode = null; Folder propertiesFolder = null; if (projectEvaluationFinished.GlobalProperties != null) { globFolder = GetOrCreateGlobalPropertiesFolder(projectEvaluation, projectEvaluationFinished.GlobalProperties); } if (projectEvaluationFinished.Items != null) { itemsNode = projectEvaluation.GetOrCreateNodeWithName<Folder>(Strings.Items, addAtBeginning: true); } if (projectEvaluationFinished.Properties != null) { propertiesFolder = projectEvaluation.GetOrCreateNodeWithName<Folder>(Strings.Properties, addAtBeginning: true); } bgJobPool.Add(new System.Threading.Tasks.Task(() => { if (projectEvaluationFinished.GlobalProperties != null && globFolder != null) { AddProperties(globFolder, (IEnumerable<KeyValuePair<string, string>>)projectEvaluationFinished.GlobalProperties, projectEvaluation); } AddProperties(propertiesFolder, projectEvaluation, projectEvaluationFinished.Properties); AddItems(itemsNode, projectEvaluation, projectEvaluationFinished.Items); })); } } } catch (Exception ex) { HandleException(ex); } } private static string GetEvaluationProjectName(int evaluationId, string projectName) => projectName; private void ConstructProfilerResult(ProjectEvaluation projectEvaluation, ProfilerResult profilerResult) { var nodes = new Dictionary<long, EvaluationProfileEntry>(); foreach (var kvp in profilerResult.ProfiledLocations) { var location = kvp.Key; var result = kvp.Value; if (!nodes.TryGetValue(location.Id, out var node)) { node = new EvaluationProfileEntry(); nodes[location.Id] = node; node.ElementName = location.ElementName; node.ElementDescription = location.ElementDescription; node.EvaluationPassDescription = location.EvaluationPassDescription; node.Kind = location.Kind; node.SourceFilePath = location.File; node.LineNumber = location.Line ?? 0; node.AddEntry(result); } } foreach (var kvp in profilerResult.ProfiledLocations) { var location = kvp.Key; if (nodes.TryGetValue(location.Id, out var node)) { if (location.ParentId.HasValue && nodes.TryGetValue(location.ParentId.Value, out var parentNode)) { parentNode.AddChild(node); var parentDuration = parentNode.ProfiledLocation.InclusiveTime.TotalMilliseconds; var duration = node.ProfiledLocation.InclusiveTime.TotalMilliseconds; double ratio = GetRatio(parentDuration, duration); node.Value = ratio; } else { projectEvaluation.AddChildAtBeginning(node); node.Value = 100; } } } } private static double GetRatio(double parentDuration, double duration) { double ratio = 100; if (parentDuration != 0) { ratio = 100 * duration / parentDuration; if (ratio < 0) { ratio = 0; } else if (ratio > 100.0) { ratio = 100.0; } } return ratio; } public void WarningRaised(object sender, BuildWarningEventArgs args) { try { lock (syncLock) { TreeNode parent = FindParent(args.BuildEventContext); if (parent == null) { parent = Build; } var warning = new Warning(); string text = args.Message; if (parent is ResolveAssemblyReferenceTask rar) { var match = Strings.IsFoundConflicts(text); if (match.Success) { string details = match.Groups[2].Value; // https://github.com/KirillOsenkov/MSBuildStructuredLog/issues/443 ItemGroupParser.ParseThereWasAConflict(warning, details, stringTable); text = text.GetFirstLine(); } } parent = parent.GetOrCreateNodeWithName<Folder>(Strings.Warnings); Populate(warning, args, text); parent.AddChild(warning); } } catch (Exception ex) { HandleException(ex); } } private TreeNode FindParent(BuildEventContext buildEventContext) { TreeNode result = null; if (buildEventContext.ProjectContextId == -2) { var evaluationId = buildEventContext.EvaluationId; result = EvaluationFolder; var projectEvaluation = result.FindChild<ProjectEvaluation>(p => p.Id == evaluationId); if (projectEvaluation != null) { result = projectEvaluation; } return result; } Project project = GetOrAddProject(buildEventContext.ProjectContextId); result = project; if (buildEventContext.TargetId > 0) { var target = project.GetTargetById(buildEventContext.TargetId); if (target != null) { result = target; if (buildEventContext.TaskId > 0) { var task = target.GetTaskById(buildEventContext.TaskId); if (task != null) { result = task; } } } } return result; } public void ErrorRaised(object sender, BuildErrorEventArgs args) { try { lock (syncLock) { TreeNode parent = FindParent(args.BuildEventContext); if (parent == null) { parent = Build; } var errors = parent.GetOrCreateNodeWithName<Folder>(Intern("Errors")); var error = new Error(); Populate(error, args); errors.AddChild(error); } } catch (Exception ex) { HandleException(ex); } } private void Populate(AbstractDiagnostic message, BuildWarningEventArgs args, string text) { message.Text = Intern(text); message.Timestamp = args.Timestamp; message.Code = Intern(args.Code); message.ColumnNumber = args.ColumnNumber; message.EndColumnNumber = args.EndColumnNumber; message.EndLineNumber = args.EndLineNumber; message.LineNumber = args.LineNumber; message.File = Intern(args.File); message.ProjectFile = Intern(args.ProjectFile); message.Subcategory = Intern(args.Subcategory); } private void Populate(AbstractDiagnostic message, BuildErrorEventArgs args) { message.Text = Intern(args.Message); message.Timestamp = args.Timestamp; message.Code = Intern(args.Code); message.ColumnNumber = args.ColumnNumber; message.EndColumnNumber = args.EndColumnNumber; message.EndLineNumber = args.EndLineNumber; message.LineNumber = args.LineNumber; message.File = Intern(args.File); message.ProjectFile = Intern(args.ProjectFile); message.Subcategory = Intern(args.Subcategory); } private void HandleException(Exception ex) { ErrorReporting.ReportException(ex); try { lock (syncLock) { Build.AddChild(new Error() { Text = ex.ToString() }); } } catch (Exception) { } } public static Project CreateProject(int id) { var result = new Project(); result.Id = id; return result; } /// <summary> /// Gets a project instance for the given identifier. Will create if it doesn't exist. /// </summary> /// <remarks>If the ProjectStartedEventArgs is not known at this time (null), a stub project is created.</remarks> /// <param name="projectId">The project identifier.</param> /// <param name="args">The <see cref="ProjectStartedEventArgs"/> instance containing the event data.</param> /// <param name="parentProject">The parent project, if any.</param> /// <returns>Project object</returns> public Project GetOrAddProject(ProjectStartedEventArgs args, Project parentProject = null) { var projectId = args.BuildEventContext.ProjectContextId; Project result = _projectIdToProjectMap.GetOrAdd(projectId, id => CreateProject(id)); result.NodeId = args.BuildEventContext.NodeId; UpdateProject(result, args); return result; } public Project GetOrAddProject(int projectId) { Project result = _projectIdToProjectMap.GetOrAdd(projectId, id => CreateProject(id)); return result; } public Project GetProject(int projectId) { _projectIdToProjectMap.TryGetValue(projectId, out var result); return result; } public Target GetTarget(int projectId, int targetId) { if (!_projectIdToProjectMap.TryGetValue(projectId, out var project)) { return null; } return project.GetTargetById(targetId); } /// <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="args">The <see cref="ProjectStartedEventArgs"/> instance containing the event data.</param> public void UpdateProject(Project project, ProjectStartedEventArgs args) { if (project.Name == null && args != null) { project.StartTime = args.Timestamp; project.Name = Intern(Path.GetFileName(args.ProjectFile)); project.ProjectFile = Intern(args.ProjectFile); project.EntryTargets = string.IsNullOrWhiteSpace(args.TargetNames) ? ImmutableArray<string>.Empty : stringTable.InternList(TextUtilities.SplitSemicolonDelimitedList(args.TargetNames)); project.TargetsText = args.TargetNames; var evaluationId = BuildEventContext.InvalidEvaluationId; if (args.BuildEventContext.EvaluationId > BuildEventContext.InvalidEvaluationId) { evaluationId = args.BuildEventContext.EvaluationId; } else if (args.ParentProjectBuildEventContext != null && args.ParentProjectBuildEventContext.EvaluationId > BuildEventContext.InvalidEvaluationId) { evaluationId = args.ParentProjectBuildEventContext.EvaluationId; } project.EvaluationId = evaluationId; if (evaluationId != BuildEventContext.InvalidEvaluationId) { project.EvaluationText = Intern("id:" + evaluationId); } project.GlobalProperties = stringTable.InternStringDictionary(args.GlobalProperties) ?? ImmutableDictionary<string, string>.Empty; // Pre-create folder before starting the fill on the background thread. Folder globalNode = null; if (args.GlobalProperties != null) { globalNode = GetOrCreateGlobalPropertiesFolder(project, project.GlobalProperties); } Folder targetsNode = null; Folder itemFolder = null; Folder propertyFolder = null; if (!string.IsNullOrEmpty(args.TargetNames)) { targetsNode = project.GetOrCreateNodeWithName<Folder>(Strings.EntryTargets); } if (args.Items != null) { itemFolder = project.GetOrCreateNodeWithName<Folder>(Strings.Items, addAtBeginning: true); } if (args.Properties != null) { propertyFolder = project.GetOrCreateNodeWithName<Folder>(Strings.Properties, addAtBeginning: true); } bgJobPool.Add(new System.Threading.Tasks.Task(() => { if (args.GlobalProperties != null && globalNode != null) { AddProperties(globalNode, args.GlobalProperties, project); } if (!string.IsNullOrEmpty(args.TargetNames)) { AddEntryTargets(targetsNode, project); } AddProperties(propertyFolder, project, args.Properties); AddItems(itemFolder, project, args.Items); })); } } public void AddMetadata(ITaskItem item, Item itemNode) { var cloned = item.CloneCustomMetadata(); if (cloned is ArrayDictionary<string, string> metadata) { int count = metadata.Count; if (count == 0) { return; } itemNode.EnsureChildrenCapacity(count); var keys = metadata.KeyArray; var values = metadata.ValueArray; for (int i = 0; i < count; i++) { var key = keys[i]; var value = values[i]; var metadataNode = new Metadata { Name = key, Value = value }; // hot path, do not use AddChild // itemNode.AddChild(metadataNode); itemNode.Children.Add(metadataNode); metadataNode.Parent = itemNode; } } else { if (cloned is ICollection collection) { itemNode.EnsureChildrenCapacity(collection.Count); } foreach (DictionaryEntry metadataName in cloned) { var metadataNode = new Metadata { Name = SoftIntern(Convert.ToString(metadataName.Key)), Value = SoftIntern(Convert.ToString(metadataName.Value)) }; itemNode.Children.Add(metadataNode); metadataNode.Parent = itemNode; } } } private void AddItems(Folder itemsNode, TreeNode parent, IEnumerable itemList) { if (itemList == null) { return; } foreach (DictionaryEntry kvp in itemList) { var itemType = SoftIntern(Convert.ToString(kvp.Key)); var itemTypeNode = itemsNode.GetOrCreateNodeWithName<AddItem>(itemType); var itemNode = new Item(); if (kvp.Value is ITaskItem taskItem) { itemNode.Text = SoftIntern(taskItem.ItemSpec); AddMetadata(taskItem, itemNode); itemTypeNode.AddChild(itemNode); } } itemsNode.SortChildren(); } private void AddProperties(Folder propertiesFolder, TreeNode project, IEnumerable properties) { if (properties == null) { return; } var list = (IEnumerable<KeyValuePair<string, string>>)properties; AddProperties( propertiesFolder, list.OrderBy(d => d.Key, StringComparer.OrdinalIgnoreCase), project as IProjectOrEvaluation); } private static HashSet<string> ignoreAssemblyForTasks = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "AssignTargetPath", "CallTarget", "Copy", "Delete", "FindUnderPath", "MakeDir", "Message", "MSBuild", "ReadLinesFromFile", "WriteLinesToFile" }; private bool IgnoreAssembly(string taskName) { return ignoreAssemblyForTasks.Contains(taskName); } private Task CreateTask(TaskStartedEventArgs taskStartedEventArgs) { var taskName = Intern(taskStartedEventArgs.TaskName); string assembly = null; if (!IgnoreAssembly(taskName)) { assembly = Intern(GetTaskAssembly(taskName)); } var taskId = taskStartedEventArgs.BuildEventContext.TaskId; var startTime = taskStartedEventArgs.Timestamp; Task result = taskName.ToLowerInvariant() switch { "copy" => new CopyTask(), "robocopy" => new RobocopyTask(), "csc" => new CscTask(), "vbc" => new VbcTask(), "fsc" => new FscTask(), "resolveassemblyreference" => new ResolveAssemblyReferenceTask(), "cl" => new CppAnalyzer.CppTask(), "lib" => new CppAnalyzer.CppTask(), "link" => new CppAnalyzer.CppTask(), "multitooltask" => new CppAnalyzer.CppTask(), _ => new Task(), }; result.Name = taskName; result.Id = taskId; result.NodeId = taskStartedEventArgs.BuildEventContext.NodeId; result.StartTime = startTime; result.FromAssembly = assembly; result.SourceFilePath = Intern(taskStartedEventArgs.TaskFile); return result; } /// <summary> /// Gets the task assembly. /// </summary> /// <param name="taskName">Name of the task.</param> /// <returns>The assembly location for the task.</returns> public string GetTaskAssembly(string taskName) { lock (_taskToAssemblyMap) { return _taskToAssemblyMap.TryGetValue(taskName, out string assembly) ? assembly : string.Empty; } } /// <summary> /// Sets the assembly location for a given task. /// </summary> /// <param name="taskName">Name of the task.</param> /// <param name="assembly">The assembly location.</param> public void SetTaskAssembly(string taskName, string assembly) { lock (_taskToAssemblyMap) { // Important to overwrite because the Using task ... message is usually logged immediately before the TaskStarted // so need to make sure we remember the last assembly used for this task // see issue https://github.com/KirillOsenkov/MSBuildStructuredLog/issues/669 _taskToAssemblyMap[taskName] = assembly; } } private Folder GetOrCreateGlobalPropertiesFolder(TreeNode project, IEnumerable globalProperties) { if (globalProperties == null) { return null; } var propertiesNode = project.GetOrCreateNodeWithName<Folder>(Strings.Properties, addAtBeginning: true); var globalNode = propertiesNode.GetOrCreateNodeWithName<Folder>(Strings.Global, addAtBeginning: true); return globalNode; } private static void AddEntryTargets(Folder targetsNode, Project project) { var entryTargets = project.EntryTargets; if (entryTargets != null) { foreach (var entryTarget in entryTargets) { var property = new EntryTarget { Name = entryTarget, }; targetsNode.AddChild(property); } } } private void AddProperties(TreeNode parent, IEnumerable<KeyValuePair<string, string>> properties, IProjectOrEvaluation project = null) { if (properties == null) { return; } if (properties is ICollection collection) { parent.EnsureChildrenCapacity(collection.Count); } bool tfvFound = false; bool platformFound = false; bool configFound = false; foreach (var kvp in properties) { var property = new Property { Name = SoftIntern(kvp.Key), Value = SoftIntern(kvp.Value) }; parent.Children.Add(property); // don't use AddChild for performance property.Parent = parent; if (project != null) { if (!tfvFound && string.Equals(kvp.Key, Strings.TargetFramework, StringComparison.OrdinalIgnoreCase)) { project.TargetFramework = kvp.Value; tfvFound = true; } else if (!tfvFound && string.Equals(kvp.Key, Strings.TargetFrameworks, StringComparison.OrdinalIgnoreCase)) { // we want TargetFramework to take precedence over TargetFrameworks when both are present if (string.IsNullOrEmpty(project.TargetFramework) && !string.IsNullOrEmpty(kvp.Value)) { project.TargetFramework = kvp.Value; tfvFound = true; } } // If neither of the above are there - look for the old project system else if (!tfvFound && project.TargetFramework is null && string.Equals(kvp.Key, Strings.TargetFrameworkVersion, StringComparison.OrdinalIgnoreCase)) { // Note this is untranslated, so e.g. "v4.6.2" instead of "net462" - this is intentional as it // renders the badge for all projects, but you can still use this difference to tell what is/isn't an SDK project. project.TargetFramework = kvp.Value; tfvFound = true; } else if (!platformFound && string.Equals(kvp.Key, Strings.Platform, StringComparison.OrdinalIgnoreCase)) { project.Platform = kvp.Value; platformFound = true; } else if (!configFound && string.Equals(kvp.Key, Strings.Configuration, StringComparison.OrdinalIgnoreCase)) { project.Configuration = kvp.Value; configFound = true; } } } } } }