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

namespace Microsoft.DotNet.HotReload.Utils.Generator;

public abstract class Runner {
    readonly protected Config config;

    protected Runner(Config config)
    {
        this.config = config;
    }

    public static Runner Make (Config config)
    {
        if (config.Live)
            return new Runners.LiveRunner (config);
        else
            return new Runners.ScriptRunner (config);
    }

    public async Task Run (CancellationToken ct = default) {
        await PrepareToRun(ct);
        var capabilities = PrepareCapabilities();
        var baselineArtifacts = await SetupBaseline (capabilities, ct);

        var deltaProject = new DeltaProject (baselineArtifacts);
        var derivedInputs = SetupDeltas (baselineArtifacts, ct);

        await GenerateDeltas (deltaProject, derivedInputs, makeOutputs: MakeOutputs, outputsReady: OutputsReady, ct: ct);
        // FIXME: do something for LiveRunner
        if (OutputsDone != null)
            await OutputsDone (ct);
    }

    /// Delegate that is called to create the delta output streams.
    /// If not set, a default is used that writes the deltas to files.
    protected  Func<DeltaNaming,DeltaOutputStreams>? MakeOutputs {get; set; } = null;

    /// Delegate that is called after the outputs have been emitted.
    /// If not set, a default is used that does nothing.
    protected  Action<DeltaNaming,DeltaOutputStreams>? OutputsReady {get; set; } = null;

    /// Called when all the outputs have been emitted.
    protected Func<CancellationToken,Task>? OutputsDone {get; set;} = null;

    private async Task<BaselineArtifacts> SetupBaseline (EnC.EditAndContinueCapabilities capabilities, CancellationToken ct = default) {
        BaselineProject baselineProject = await BaselineProject.Make (config, capabilities, ct);

        var baselineArtifacts = await baselineProject.PrepareBaseline(ct);

        Console.WriteLine ("baseline ready");
        return baselineArtifacts;
    }

    /// Called just before we start generating deltas.
    protected abstract Task PrepareToRun(CancellationToken ct = default);

    /// Returns true if the runner has capabilities for the project, or false to use the config defaults.
    protected abstract bool PrepareCapabilitiesCore (out EnC.EditAndContinueCapabilities capabilities);

    protected EnC.EditAndContinueCapabilities PrepareCapabilities() {
        EnC.EditAndContinueCapabilities configCaps = EnC.EditAndContinueCapabilities.None;
        (var configuredCaps, var unknowns) = EditAndContinueCapabilitiesParser.Parse (config.EditAndContinueCapabilities);
        foreach (var c in configuredCaps) {
            configCaps |= c;
        }
        bool projectHasCaps = PrepareCapabilitiesCore (out var runnerCaps);
        var totalCaps = configCaps | runnerCaps;
        // If the project explicitly sets no capabilities, use None
        if (totalCaps == EnC.EditAndContinueCapabilities.None && !projectHasCaps)
            totalCaps = DefaultCapabilities ();
        if (!config.NoWarnUnknownCapabilities) {
            foreach (var unk in unknowns) {
                Console.WriteLine ("Unknown EnC capability '{0}', ignored.", unk);
            }
        }
        return totalCaps;
    }

    protected EnC.EditAndContinueCapabilities DefaultCapabilities ()
    {
        var allCaps = EnC.EditAndContinueCapabilities.Baseline
            | EnC.EditAndContinueCapabilities.AddMethodToExistingType
            | EnC.EditAndContinueCapabilities.AddStaticFieldToExistingType
            | EnC.EditAndContinueCapabilities.AddInstanceFieldToExistingType
            | EnC.EditAndContinueCapabilities.NewTypeDefinition
            | EnC.EditAndContinueCapabilities.ChangeCustomAttributes
            | EnC.EditAndContinueCapabilities.UpdateParameters
            | EnC.EditAndContinueCapabilities.GenericAddMethodToExistingType
            | EnC.EditAndContinueCapabilities.GenericUpdateMethod
            | EnC.EditAndContinueCapabilities.GenericAddFieldToExistingType
            | EnC.EditAndContinueCapabilities.AddExplicitInterfaceImplementation
            | EnC.EditAndContinueCapabilities.AddFieldRva
            ;
        return allCaps;
    }

    private protected abstract IAsyncEnumerable<Delta> SetupDeltas (BaselineArtifacts baselineArtifacts, CancellationToken ct = default);

    private async Task GenerateDeltas (DeltaProject deltaProject, IAsyncEnumerable<Delta> deltas,
                                        Func<DeltaNaming,DeltaOutputStreams>? makeOutputs = null,
                                        Action<DeltaNaming, DeltaOutputStreams>? outputsReady = null,
                                        CancellationToken ct =  default)
    {
        await foreach (var delta in deltas.WithCancellation(ct)) {
            Console.WriteLine ("got a change");
            /* fixme: why does FSW sometimes queue up 2 events in quick succession after a single save? */
            deltaProject = await deltaProject.BuildDelta (delta, ignoreUnchanged: config.Live, makeOutputs: makeOutputs, outputsReady: outputsReady, ct: ct);
        }
    }

}