File: BackEnd\TaskHostBuildResponse.cs
Web Access
Project: 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.Collections.Generic;
using Microsoft.Build.Framework;

namespace Microsoft.Build.BackEnd;

/// <summary>
/// Response packet from owning worker node to TaskHost with BuildProjectFile* results.
/// Carries the build success/failure and target outputs per project.
/// </summary>
internal class TaskHostBuildResponse : INodePacket, ITaskHostCallbackPacket
{
    private int _requestId;
    private bool _success;

    /// <summary>
    /// Target outputs per project. Each entry is a dictionary mapping target names to TaskParameter
    /// wrapping ITaskItem[] outputs. Uses the same TaskParameter serialization as TaskHostTaskComplete.
    /// </summary>
    private List<Dictionary<string, TaskParameter>>? _targetOutputsPerProject;

    public TaskHostBuildResponse()
    {
    }

    public TaskHostBuildResponse(int requestId, bool success, List<Dictionary<string, TaskParameter>>? targetOutputsPerProject)
    {
        _requestId = requestId;
        _success = success;
        _targetOutputsPerProject = targetOutputsPerProject;
    }

    public NodePacketType Type => NodePacketType.TaskHostBuildResponse;

    public int RequestId
    {
        get => _requestId;
        set => _requestId = value;
    }

    /// <summary>Whether the build succeeded.</summary>
    public bool Success => _success;

    /// <summary>Per-project target outputs, or null if outputs were not requested.</summary>
    public List<Dictionary<string, TaskParameter>>? TargetOutputsPerProject => _targetOutputsPerProject;

    /// <summary>
    /// Reconstructs a <see cref="BuildEngineResult"/> from this response packet.
    /// Converts <see cref="TaskParameter"/> values back to <see cref="ITaskItem"/>[] arrays.
    /// </summary>
    public BuildEngineResult ToBuildEngineResult()
    {
        List<IDictionary<string, ITaskItem[]>>? result = null;

        if (_targetOutputsPerProject is not null)
        {
            result = new List<IDictionary<string, ITaskItem[]>>(_targetOutputsPerProject.Count);

            foreach (Dictionary<string, TaskParameter> projectOutputs in _targetOutputsPerProject)
            {
                var dict = new Dictionary<string, ITaskItem[]>(projectOutputs.Count, StringComparer.OrdinalIgnoreCase);

                if (projectOutputs is not null)
                {
                    foreach (KeyValuePair<string, TaskParameter> entry in projectOutputs)
                    {
                        dict[entry.Key] = (ITaskItem[]?)entry.Value?.WrappedParameter ?? [];
                    }
                }

                result.Add(dict);
            }
        }

        return new BuildEngineResult(_success, result ?? []);
    }

    /// <summary>
    /// Creates a response from a <see cref="BuildEngineResult"/>.
    /// Wraps <see cref="ITaskItem"/>[] arrays in <see cref="TaskParameter"/> for serialization.
    /// </summary>
    internal static TaskHostBuildResponse FromBuildEngineResult(int requestId, BuildEngineResult engineResult)
    {
        List<Dictionary<string, TaskParameter>>? outputs = null;

        if (engineResult.TargetOutputsPerProject is not null && engineResult.TargetOutputsPerProject.Count > 0)
        {
            outputs = new List<Dictionary<string, TaskParameter>>(engineResult.TargetOutputsPerProject.Count);

            foreach (IDictionary<string, ITaskItem[]> projectOutputs in engineResult.TargetOutputsPerProject)
            {
                var dict = new Dictionary<string, TaskParameter>(projectOutputs?.Count ?? 0, StringComparer.OrdinalIgnoreCase);

                if (projectOutputs is not null)
                {
                    foreach (KeyValuePair<string, ITaskItem[]> entry in projectOutputs)
                    {
                        dict[entry.Key] = new TaskParameter(entry.Value);
                    }
                }

                outputs.Add(dict);
            }
        }

        return new TaskHostBuildResponse(requestId, engineResult.Result, outputs);
    }

    public void Translate(ITranslator translator)
    {
        translator.Translate(ref _requestId);
        translator.Translate(ref _success);
        TranslateTargetOutputs(translator);
    }

    private void TranslateTargetOutputs(ITranslator translator)
    {
        bool hasOutputs = _targetOutputsPerProject is not null;
        translator.Translate(ref hasOutputs);

        if (!hasOutputs)
        {
            _targetOutputsPerProject = null;
            return;
        }

        int count = _targetOutputsPerProject?.Count ?? 0;
        translator.Translate(ref count);

        if (translator.Mode == TranslationDirection.ReadFromStream)
        {
            _targetOutputsPerProject = new List<Dictionary<string, TaskParameter>>(count);
            for (int i = 0; i < count; i++)
            {
                Dictionary<string, TaskParameter>? dict = null;
                translator.TranslateDictionary(ref dict, StringComparer.OrdinalIgnoreCase, TaskParameter.FactoryForDeserialization);
                _targetOutputsPerProject.Add(dict!);
            }
        }
        else
        {
            for (int i = 0; i < count; i++)
            {
                Dictionary<string, TaskParameter>? dict = _targetOutputsPerProject![i];
                translator.TranslateDictionary(ref dict, StringComparer.OrdinalIgnoreCase, TaskParameter.FactoryForDeserialization);
            }
        }
    }

    internal static INodePacket FactoryForDeserialization(ITranslator translator)
    {
        var packet = new TaskHostBuildResponse();
        packet.Translate(translator);
        return packet;
    }
}