File: BackEnd\Components\Logging\ProjectLoggingContext.cs
Web Access
Project: ..\..\..\src\Build\Microsoft.Build.csproj (Microsoft.Build)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using Microsoft.Build.Collections;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using static Microsoft.Build.Execution.ProjectPropertyInstance;
using TaskItem = Microsoft.Build.Execution.ProjectItemInstance.TaskItem;
 
#nullable disable
 
namespace Microsoft.Build.BackEnd.Logging
{
    /// <summary>
    /// A logging context for a project.
    /// </summary>
    internal class ProjectLoggingContext : BuildLoggingContext
    {
        /// <summary>
        /// The project's full path
        /// </summary>
        private string _projectFullPath;
 
        /// <summary>
        /// Constructs a project logging context.
        /// </summary>
        internal ProjectLoggingContext(NodeLoggingContext nodeLoggingContext, BuildRequestEntry requestEntry)
            : this
            (
            nodeLoggingContext,
            requestEntry.Request.SubmissionId,
            requestEntry.Request.ConfigurationId,
            requestEntry.RequestConfiguration.ProjectFullPath,
            requestEntry.Request.Targets,
            requestEntry.RequestConfiguration.ToolsVersion,
            requestEntry.RequestConfiguration.Project.PropertiesToBuildWith,
            requestEntry.RequestConfiguration.Project.ItemsToBuildWith,
            requestEntry.Request.ParentBuildEventContext,
            requestEntry.RequestConfiguration.Project.EvaluationId,
            requestEntry.Request.ProjectContextId)
        {
        }
 
        /// <summary>
        /// Constructs a project logging context.
        /// </summary>
        internal ProjectLoggingContext(
            NodeLoggingContext nodeLoggingContext,
            BuildRequest request,
            string projectFullPath,
            string toolsVersion,
            int evaluationId = BuildEventContext.InvalidEvaluationId)
            : this
            (
            nodeLoggingContext,
            request.SubmissionId,
            request.ConfigurationId,
            projectFullPath,
            request.Targets,
            toolsVersion,
            projectProperties: null,
            projectItems: null,
            request.ParentBuildEventContext,
            evaluationId,
            request.ProjectContextId)
        {
        }
 
        /// <summary>
        /// Creates ProjectLoggingContext, without logging ProjectStartedEventArgs as a side effect.
        /// The ProjectStartedEventArgs is returned as well - so that it can be later logged explicitly
        /// </summary>
        public static (ProjectStartedEventArgs, ProjectLoggingContext) CreateLoggingContext(
            NodeLoggingContext nodeLoggingContext, BuildRequestEntry requestEntry)
        {
            ProjectStartedEventArgs args = CreateProjectStarted(
                nodeLoggingContext,
                requestEntry.Request.SubmissionId,
                requestEntry.Request.ConfigurationId,
                requestEntry.RequestConfiguration.ProjectFullPath,
                requestEntry.Request.Targets,
                requestEntry.RequestConfiguration.ToolsVersion,
                requestEntry.RequestConfiguration.Project.PropertiesToBuildWith,
                requestEntry.RequestConfiguration.Project.ItemsToBuildWith,
                requestEntry.Request.ParentBuildEventContext,
                requestEntry.RequestConfiguration.Project.EvaluationId,
                requestEntry.Request.ProjectContextId);
 
            return (args, new ProjectLoggingContext(nodeLoggingContext, args));
        }
 
        private ProjectLoggingContext(
            NodeLoggingContext nodeLoggingContext,
            ProjectStartedEventArgs projectStarted)
        : base(nodeLoggingContext, projectStarted.BuildEventContext)
        {
            _projectFullPath = projectStarted.ProjectFile;
 
            // No need to log a redundant message in the common case
            if (projectStarted.ToolsVersion != "Current")
            {
                LoggingService.LogComment(this.BuildEventContext, MessageImportance.Low, "ToolsVersionInEffectForBuild", projectStarted.ToolsVersion);
            }
 
            this.IsValid = true;
        }
 
        /// <summary>
        /// Constructs a project logging contexts.
        /// </summary>
        private ProjectLoggingContext(
            NodeLoggingContext nodeLoggingContext,
            int submissionId,
            int configurationId,
            string projectFullPath,
            List<string> targets,
            string toolsVersion,
            PropertyDictionary<ProjectPropertyInstance> projectProperties,
            IItemDictionary<ProjectItemInstance> projectItems,
            BuildEventContext parentBuildEventContext,
            int evaluationId,
            int projectContextId)
            : base(nodeLoggingContext,
                CreateInitialContext(nodeLoggingContext,
                    submissionId,
                     configurationId,
                    projectFullPath,
                    targets,
                    toolsVersion,
                    projectProperties,
                    projectItems,
                    parentBuildEventContext,
                    evaluationId,
                    projectContextId))
        {
            _projectFullPath = projectFullPath;
 
            // No need to log a redundant message in the common case
            if (toolsVersion != "Current")
            {
                LoggingService.LogComment(this.BuildEventContext, MessageImportance.Low, "ToolsVersionInEffectForBuild", toolsVersion);
            }
 
            this.IsValid = true;
        }
 
        private static BuildEventContext CreateInitialContext(
            NodeLoggingContext nodeLoggingContext,
            int submissionId,
            int configurationId,
            string projectFullPath,
            List<string> targets,
            string toolsVersion,
            PropertyDictionary<ProjectPropertyInstance> projectProperties,
            IItemDictionary<ProjectItemInstance> projectItems,
            BuildEventContext parentBuildEventContext,
            int evaluationId,
            int projectContextId)
        {
            ProjectStartedEventArgs args = CreateProjectStarted(
                nodeLoggingContext,
                submissionId,
                configurationId,
                projectFullPath,
                targets,
                toolsVersion,
                projectProperties,
                projectItems,
                parentBuildEventContext,
                evaluationId,
                projectContextId);
 
            nodeLoggingContext.LoggingService.LogProjectStarted(args);
 
            return args.BuildEventContext;
        }
 
        private static ProjectStartedEventArgs CreateProjectStarted(
            NodeLoggingContext nodeLoggingContext,
            int submissionId,
            int configurationId,
            string projectFullPath,
            List<string> targets,
            string toolsVersion,
            PropertyDictionary<ProjectPropertyInstance> projectProperties,
            IItemDictionary<ProjectItemInstance> projectItems,
            BuildEventContext parentBuildEventContext,
            int evaluationId,
            int projectContextId)
        {
            IEnumerable<DictionaryEntry> properties = null;
            IEnumerable<DictionaryEntry> items = null;
 
            ILoggingService loggingService = nodeLoggingContext.LoggingService;
 
            string[] propertiesToSerialize = loggingService.PropertiesToSerialize;
 
            // If we are only logging critical events lets not pass back the items or properties
            if (!loggingService.OnlyLogCriticalEvents &&
                loggingService.IncludeEvaluationPropertiesAndItemsInProjectStartedEvent &&
                (!loggingService.RunningOnRemoteNode || loggingService.SerializeAllProperties))
            {
                if (projectProperties is null)
                {
                    properties = [];
                }
                else if (Traits.LogAllEnvironmentVariables)
                {
                    properties = projectProperties.GetCopyOnReadEnumerable(property => new DictionaryEntry(property.Name, property.EvaluatedValue));
                }
                else
                {
                    properties = projectProperties.Filter(p => p is not EnvironmentDerivedProjectPropertyInstance || EnvironmentUtilities.IsWellKnownEnvironmentDerivedProperty(p.Name), p => new DictionaryEntry(p.Name, p.EvaluatedValue));
                }
 
                items = projectItems?.GetCopyOnReadEnumerable(item => new DictionaryEntry(item.ItemType, new TaskItem(item))) ?? [];
            }
 
            if (projectProperties != null &&
                loggingService.IncludeEvaluationPropertiesAndItemsInProjectStartedEvent &&
                propertiesToSerialize?.Length > 0 &&
                !loggingService.SerializeAllProperties)
            {
                PropertyDictionary<ProjectPropertyInstance> projectPropertiesToSerialize = new PropertyDictionary<ProjectPropertyInstance>();
                foreach (string propertyToGet in propertiesToSerialize)
                {
                    ProjectPropertyInstance instance = projectProperties[propertyToGet];
                    {
                        if (instance != null)
                        {
                            projectPropertiesToSerialize.Set(instance);
                        }
                    }
                }
 
                properties = projectPropertiesToSerialize.Select((ProjectPropertyInstance property) => new DictionaryEntry(property.Name, property.EvaluatedValue));
            }
 
            return loggingService.CreateProjectStarted(
                nodeLoggingContext.BuildEventContext,
                submissionId,
                configurationId,
                parentBuildEventContext,
                projectFullPath,
                string.Join(";", targets),
                properties,
                items,
                evaluationId,
                projectContextId);
        }
 
        /// <summary>
        /// Log that the project has finished
        /// </summary>
        /// <param name="success">Did the build succeede or not</param>
        internal void LogProjectFinished(bool success)
        {
            ErrorUtilities.VerifyThrow(this.IsValid, "invalid");
            LoggingService.LogProjectFinished(BuildEventContext, _projectFullPath, success);
            this.IsValid = false;
        }
 
        /// <summary>
        /// Log that a target has started
        /// </summary>
        internal TargetLoggingContext LogTargetBatchStarted(string projectFullPath, ProjectTargetInstance target, string parentTargetName, TargetBuiltReason buildReason)
        {
            ErrorUtilities.VerifyThrow(this.IsValid, "invalid");
            return new TargetLoggingContext(this, projectFullPath, target, parentTargetName, buildReason);
        }
    }
}