File: HotReloadDeltaGeneratorComputeScriptOutputs.cs
Web Access
Project: src\src\runtime\src\tools\hotreload-delta-gen\Microsoft.DotNet.HotReload.Utils.Generator.Tasks\Microsoft.DotNet.HotReload.Utils.Generator.Tasks.csproj (Microsoft.DotNet.HotReload.Utils.Generator.Tasks)
// 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 System.Threading;
using System.Threading.Tasks;
using System.Text.Json;

using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;


namespace Microsoft.DotNet.HotReload.Utils.Generator.Tasks;

/// Given a DeltaScript, counts the number of elements and returns items for the .dmeta, .dil, and .dpdb
/// files that the Generator would produce.
public class HotReloadDeltaGeneratorComputeScriptOutputs : Microsoft.Build.Utilities.Task
{
    /// The name of the assembly produced by the current project
    [Required]
    public string BaseAssemblyName { get; set; }
    /// The name of the json delta script
    [Required]
    public string DeltaScript {get; set; }


    /// The generated delta outputs
    ///  Each item has a DeltaOutputType metadata with a value of "dmeta", "dil", "dpdb" or "updateHandlerJson"
    ///    indicating what kind of delta output it is.
    [Output]
    public ITaskItem[] DeltaOutputs { get; set; }

    /// The (relative to the script file) sources that comprise the changes.
    ///  Each item has a DeltaForBaseline metadata that has the name of the baseline source file
    [Output]
    public ITaskItem[] DeltaSources { get; set; }

    public HotReloadDeltaGeneratorComputeScriptOutputs()
    {
        BaseAssemblyName = string.Empty;
        DeltaScript = string.Empty;
        DeltaOutputs = Array.Empty<ITaskItem>();
        DeltaSources = Array.Empty<ITaskItem>();
    }

    enum DeltaOutputType {
        dmeta,
        dil,
        dpdb,
        updateHandlerJson,
    }

    public override bool Execute()
    {
        if (!System.IO.File.Exists(DeltaScript))
        {
            Log.LogError("Hot reload delta script {0} does not exist", DeltaScript);
            return false;
        }
        string baseAssemblyName = BaseAssemblyName;
        Script.Json.Script? json;
        try
        {
            json = Parse(DeltaScript).Result;
            if (json?.Changes == null) {
                Log.LogError("Hot reload delta script had no 'changes' array");
                return false;
            }
        }
        catch (JsonException exn)
        {
            Log.LogErrorFromException(exn, showStackTrace: true);
            return false;
        }

        DeltaOutputs = ComputeOutputs (baseAssemblyName, json.Changes.Length);
        DeltaSources = ComputeSources (json.Changes);
        return true;
    }

    private static ITaskItem[] ComputeOutputs (string baseAssemblyName, int count)
    {
        const string deltaOutputTypeMetadata = "DeltaOutputType";
        DeltaOutputType[] outputTypes = new DeltaOutputType[] {
            DeltaOutputType.dmeta,
            DeltaOutputType.dil,
            DeltaOutputType.dpdb,
            DeltaOutputType.updateHandlerJson,
        };
        int itemsPerRev = outputTypes.Length;
        ITaskItem[] result = new TaskItem[itemsPerRev*count];
        for (int i = 0; i < count; ++i)
        {
            int rev = 1+i;
            foreach (var outputType in outputTypes)
            {
                int index = i*itemsPerRev + (int)outputType;
                string name = NameForOutput(baseAssemblyName, rev, outputType);
                result[index] = new TaskItem(name, new Dictionary<string,string> { { deltaOutputTypeMetadata, outputType.ToString() } });
            }
        }
        return result;
    }

    private static string NameForOutput(string baseName, int rev, DeltaOutputType t)
    {
        string ext = t switch {
            DeltaOutputType.dmeta => "dmeta",
            DeltaOutputType.dil => "dil",
            DeltaOutputType.dpdb => "dpdb",
            DeltaOutputType.updateHandlerJson => "handler.json",
            _ => throw new Exception("unexpected")
        };
        return $"{baseName}.{rev}.{ext}";
    }

    private static ITaskItem[] ComputeSources (Script.Json.Change[] changes)
    {
        var count = changes.Length;
        ITaskItem[] result = new TaskItem[changes.Length];
        for (int i = 0; i < count; ++i)
        {
            // Just return the "update" documents.  The baseline document should already be a <Compile> item in the project
            result[i] = new TaskItem(changes[i].Update, new Dictionary<string,string> {  { "DeltaForBaseline", changes[i].Document} });
        }
        return result;
    }

    public static async Task<Script.Json.Script?> Parse(string scriptPath, CancellationToken ct = default)
    {
        using var stream = System.IO.File.OpenRead(scriptPath);
        var options = new JsonSerializerOptions {
            ReadCommentHandling = JsonCommentHandling.Skip,
            AllowTrailingCommas = true,
            PropertyNameCaseInsensitive = true,
        };
        var json = await JsonSerializer.DeserializeAsync<Script.Json.Script>(stream, options: options, cancellationToken: ct);
        return json;
    }

}