File: Layout\LayoutProcessRunner.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 Aspire.Shared;
 
namespace Aspire.Cli.Layout;
 
/// <summary>
/// Helper to detect the current runtime identifier.
/// Delegates to shared BundleDiscovery for consistent behavior.
/// </summary>
internal static class RuntimeIdentifierHelper
{
    /// <summary>
    /// Gets the current platform's runtime identifier.
    /// </summary>
    public static string GetCurrent() => BundleDiscovery.GetCurrentRuntimeIdentifier();
 
    /// <summary>
    /// Gets the archive extension for the current platform.
    /// </summary>
    public static string GetArchiveExtension() => BundleDiscovery.GetArchiveExtension();
}
 
/// <summary>
/// Utilities for running processes using the layout's .NET runtime.
/// Supports both native executables and framework-dependent DLLs.
/// </summary>
internal static class LayoutProcessRunner
{
    /// <summary>
    /// Determines if a path refers to a DLL that needs dotnet to run.
    /// </summary>
    private static bool IsDll(string path) => path.EndsWith(".dll", StringComparison.OrdinalIgnoreCase);
 
    /// <summary>
    /// Runs a tool and captures output. Automatically detects if the tool
    /// is a DLL (needs muxer) or native executable (runs directly).
    /// </summary>
    public static async Task<(int ExitCode, string Output, string Error)> RunAsync(
        LayoutConfiguration layout,
        string toolPath,
        IEnumerable<string> arguments,
        string? workingDirectory = null,
        IDictionary<string, string>? environmentVariables = null,
        CancellationToken ct = default)
    {
        using var process = CreateProcess(layout, toolPath, arguments, workingDirectory, environmentVariables, redirectOutput: true);
 
        process.Start();
 
        var outputTask = process.StandardOutput.ReadToEndAsync(ct);
        var errorTask = process.StandardError.ReadToEndAsync(ct);
 
        await process.WaitForExitAsync(ct);
 
        return (process.ExitCode, await outputTask, await errorTask);
    }
 
    /// <summary>
    /// Starts a process without waiting for it to exit.
    /// Returns the Process object for the caller to manage.
    /// </summary>
    public static Process Start(
        LayoutConfiguration layout,
        string toolPath,
        IEnumerable<string> arguments,
        string? workingDirectory = null,
        IDictionary<string, string>? environmentVariables = null,
        bool redirectOutput = false)
    {
        var process = CreateProcess(layout, toolPath, arguments, workingDirectory, environmentVariables, redirectOutput);
        process.Start();
        return process;
    }
 
    /// <summary>
    /// Creates a configured Process for running a bundle tool.
    /// For DLLs, uses the layout's muxer (dotnet). For executables, runs directly.
    /// </summary>
    private static Process CreateProcess(
        LayoutConfiguration layout,
        string toolPath,
        IEnumerable<string> arguments,
        string? workingDirectory,
        IDictionary<string, string>? environmentVariables,
        bool redirectOutput)
    {
        var isDll = IsDll(toolPath);
        var process = new Process();
 
        process.StartInfo.UseShellExecute = false;
        process.StartInfo.CreateNoWindow = true;
 
        if (isDll)
        {
            // DLLs need the muxer to run
            var muxerPath = layout.GetMuxerPath()
                ?? throw new InvalidOperationException("Layout muxer not found. Cannot run framework-dependent tool.");
            process.StartInfo.FileName = muxerPath;
            process.StartInfo.ArgumentList.Add(toolPath);
        }
        else
        {
            // Native executables run directly
            process.StartInfo.FileName = toolPath;
        }
 
        if (redirectOutput)
        {
            process.StartInfo.RedirectStandardOutput = true;
            process.StartInfo.RedirectStandardError = true;
        }
 
        // Set DOTNET_ROOT to use the layout's runtime
        var runtimePath = layout.GetComponentPath(LayoutComponent.Runtime);
        if (runtimePath is not null)
        {
            process.StartInfo.Environment["DOTNET_ROOT"] = runtimePath;
            process.StartInfo.Environment["DOTNET_MULTILEVEL_LOOKUP"] = "0";
        }
 
        // Add custom environment variables
        if (environmentVariables is not null)
        {
            foreach (var (key, value) in environmentVariables)
            {
                process.StartInfo.Environment[key] = value;
            }
        }
 
        if (workingDirectory is not null)
        {
            process.StartInfo.WorkingDirectory = workingDirectory;
        }
 
        // Add arguments
        foreach (var arg in arguments)
        {
            process.StartInfo.ArgumentList.Add(arg);
        }
 
        return process;
    }
}