File: Utils\AppHostHelper.cs
Web Access
Project: src\src\Aspire.Cli\Aspire.Cli.csproj (aspire)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Globalization;
using Aspire.Cli.DotNet;
using Aspire.Cli.Interaction;
using Aspire.Cli.Resources;
using Aspire.Cli.Telemetry;
using Semver;
 
namespace Aspire.Cli.Utils;
 
internal static class AppHostHelper
{
    internal static async Task<(bool IsCompatibleAppHost, string? AspireHostingVersion)> CheckAppHostCompatibilityAsync(IDotNetCliRunner runner, IInteractionService interactionService, FileInfo projectFile, AspireCliTelemetry telemetry, DirectoryInfo workingDirectory, string logFilePath, CancellationToken cancellationToken)
    {
        var appHostInformation = await GetAppHostInformationAsync(runner, interactionService, projectFile, telemetry, workingDirectory, cancellationToken);
 
        return EvaluateAppHostCompatibility(
            appHostInformation.ExitCode,
            appHostInformation.IsAspireHost,
            appHostInformation.AspireHostingVersion,
            interactionService,
            logFilePath);
    }
 
    /// <summary>
    /// Applies the SemVer minimum-version gate (and user-facing error display) for an AppHost
    /// using already-fetched project information. Use this when the caller has cached the
    /// MSBuild result and wants to avoid issuing another <c>dotnet msbuild -getProperty</c>
    /// invocation to evaluate compatibility.
    /// </summary>
    internal static (bool IsCompatibleAppHost, string? AspireHostingVersion) EvaluateAppHostCompatibility(
        int exitCode,
        bool isAspireHost,
        string? aspireHostingVersion,
        IInteractionService interactionService,
        string logFilePath)
    {
        if (exitCode != 0)
        {
            interactionService.DisplayError(string.Format(CultureInfo.CurrentCulture, ErrorStrings.ProjectCouldNotBeAnalyzed, logFilePath));
            return (false, null);
        }
 
        if (!isAspireHost)
        {
            interactionService.DisplayError(ErrorStrings.ProjectIsNotAppHost);
            return (false, null);
        }
 
        if (!SemVersion.TryParse(aspireHostingVersion, out var aspireVersion))
        {
            interactionService.DisplayError(ErrorStrings.CouldNotParseAspireSDKVersion);
            return (false, null);
        }
 
        var minimumVersion = SemVersion.Parse("9.2.0");
        if (aspireVersion.ComparePrecedenceTo(minimumVersion) < 0)
        {
            interactionService.DisplayError(string.Format(CultureInfo.CurrentCulture, ErrorStrings.AspireSDKVersionNotSupported, aspireHostingVersion));
            return (false, aspireHostingVersion);
        }
 
        // NOTE: When we go to support < 9.2.0 app hosts this is where we'll make
        //       a determination as to whether the apphost supports backchannel or not.
        return (true, aspireHostingVersion);
    }
 
    internal static async Task<(int ExitCode, bool IsAspireHost, string? AspireHostingVersion)> GetAppHostInformationAsync(IDotNetCliRunner runner, IInteractionService interactionService, FileInfo projectFile, AspireCliTelemetry telemetry, DirectoryInfo workingDirectory, CancellationToken cancellationToken)
    {
        using var activity = telemetry.StartDiagnosticActivity(kind: ActivityKind.Client);
 
        var relativePath = Path.GetRelativePath(workingDirectory.FullName, projectFile.FullName);
        var appHostInformationResult = await interactionService.ShowStatusAsync(
            $"{InteractionServiceStrings.CheckingProjectType}: {relativePath}",
            () => runner.GetAppHostInformationAsync(
                projectFile,
                new ProcessInvocationOptions(),
                cancellationToken),
            emoji: KnownEmojis.Microscope);
 
        return appHostInformationResult;
    }
 
    internal static async Task<int> BuildAppHostAsync(IDotNetCliRunner runner, IInteractionService interactionService, FileInfo projectFile, bool noRestore, IDictionary<string, string>? env, ProcessInvocationOptions options, DirectoryInfo workingDirectory, CancellationToken cancellationToken)
    {
        var relativePath = Path.GetRelativePath(workingDirectory.FullName, projectFile.FullName);
        return await interactionService.ShowStatusAsync(
            $"{InteractionServiceStrings.BuildingAppHost} {relativePath}",
            () => runner.BuildAsync(
                projectFile,
                noRestore,
                env,
                options,
                cancellationToken),
            emoji: KnownEmojis.HammerAndWrench);
    }
 
}