using Aspire.Hosting.Lifecycle; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace AppHost; // https://github.com/maddymontaquila/Godot-SnakeCS/blob/maddy/aspire/AppHost/GodotExtensions.cs public static class GodotExtensions { /// /// Adds a Godot project to the application model. /// /// The distributed application builder. /// The path to the Godot project file (.godot). /// The name of the resource. /// Optional arguments to pass to the Godot executable. /// A reference to the Godot project resource. public static IResourceBuilder AddGodot( this IDistributedApplicationBuilder builder, string projectPath, string? name = null, params string[] args) { name ??= Path.GetFileNameWithoutExtension(projectPath); var projectDirectory = Path.GetDirectoryName(projectPath) ?? "."; var godotPath = GetGodotExecutablePath(); // Build the arguments list var arguments = new List { "--path", projectDirectory, //"--verbose" }; // Add any custom args arguments.AddRange(args); // Create a standard ExecutableResource (since custom ones don't seem to work well) var godotResource = builder.AddExecutable( name: name, command: godotPath, workingDirectory: projectDirectory, args: arguments.ToArray()); //.WithEnvironment("OTEL_EXPORTER_OTLP_ENDPOINT", Environment.GetEnvironmentVariable("OTEL_EXPORTER_OTLP_ENDPOINT") ?? throw new InvalidOperationException("OTEL_EXPORTER_OTLP_ENDPOINT environment variable is not set")); // Add a lifecycle hook to handle building before startup builder.Services.AddSingleton(sp => new GodotBuildHook( projectPath, projectDirectory, sp.GetRequiredService>())); return godotResource; } /// /// Gets the Godot executable path based on the current platform. /// private static string GetGodotExecutablePath() { // First check for GODOT environment variable var godotPath = Environment.GetEnvironmentVariable("GODOT"); if (!string.IsNullOrWhiteSpace(godotPath)) { return godotPath; } // Fallback to platform-specific defaults if (OperatingSystem.IsMacOS()) { return "godot"; } else if (OperatingSystem.IsWindows()) { return "godot.exe"; } else if (OperatingSystem.IsLinux()) { return "godot"; } throw new PlatformNotSupportedException("Current platform is not supported for Godot execution."); } /// /// Lifecycle hook to build the Godot project before launching /// private class GodotBuildHook : IDistributedApplicationLifecycleHook { private readonly string _projectPath; private readonly string _projectDirectory; private readonly ILogger _logger; public GodotBuildHook(string projectPath, string projectDirectory, ILogger logger) { _projectPath = projectPath; _projectDirectory = projectDirectory; _logger = logger; } public async Task BeforeStartAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default) { _logger.LogInformation("Building Godot project: {ProjectPath}", _projectPath); // Execute dotnet build on the project var buildProcess = new System.Diagnostics.Process { StartInfo = new System.Diagnostics.ProcessStartInfo { FileName = "dotnet", Arguments = $"build \"{_projectPath}\" --configuration Debug", UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = true, RedirectStandardError = true, WorkingDirectory = _projectDirectory } }; buildProcess.OutputDataReceived += (sender, e) => { if (!string.IsNullOrEmpty(e.Data)) { _logger.LogInformation("[Godot Build] {Data}", e.Data); } }; buildProcess.ErrorDataReceived += (sender, e) => { if (!string.IsNullOrEmpty(e.Data)) { _logger.LogError("[Godot Build] {Data}", e.Data); } }; buildProcess.Start(); buildProcess.BeginOutputReadLine(); buildProcess.BeginErrorReadLine(); await buildProcess.WaitForExitAsync(cancellationToken); if (buildProcess.ExitCode != 0) { _logger.LogError("Failed to build Godot project: {ProjectPath}", _projectPath); return; } _logger.LogInformation("Successfully built Godot project: {ProjectPath}", _projectPath); } public Task AfterStartAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default) { return Task.CompletedTask; } } }