| File: GoLanguageSupport.cs | Web Access |
| Project: src\src\Aspire.Hosting.CodeGeneration.Go\Aspire.Hosting.CodeGeneration.Go.csproj (Aspire.Hosting.CodeGeneration.Go) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Aspire.Shared; using Aspire.TypeSystem; namespace Aspire.Hosting.CodeGeneration.Go; /// <summary> /// Provides language support for Go AppHosts. /// Implements scaffolding, detection, and runtime configuration. /// </summary> internal sealed class GoLanguageSupport : ILanguageSupport { /// <summary> /// The language/runtime identifier for Go. /// </summary> private const string LanguageId = "go"; /// <summary> /// The code generation target language. This maps to the ICodeGenerator.Language property. /// </summary> private const string CodeGenTarget = "Go"; private const string LanguageDisplayName = "Go"; private static readonly string[] s_detectionPatterns = ["apphost.go"]; /// <inheritdoc /> public string Language => LanguageId; /// <inheritdoc /> public Dictionary<string, string> Scaffold(ScaffoldRequest request) { var files = new Dictionary<string, string>(); // Create apphost.go. The blank line before the closing quotes is intentional: a raw string literal // drops the last newline, and gofmt requires the file to end with one. files["apphost.go"] = """ // Aspire Go AppHost // For more information, see: https://aspire.dev package main import ( "log" "apphost/modules/aspire" ) func main() { builder, err := aspire.CreateBuilder() if err != nil { log.Fatal(aspire.FormatError(err)) } // Add your resources here. Each call records its failure on the handle that it returns, // so check every resource, for example: // cache := builder.AddRedis("cache") // db := builder.AddPostgres("db") // for _, resource := range []aspire.Resource{cache, db} { // if err := resource.Err(); err != nil { // log.Fatal(aspire.FormatError(err)) // } // } app, err := builder.Build() if err != nil { log.Fatal(aspire.FormatError(err)) } if err := app.Run(); err != nil { log.Fatal(aspire.FormatError(err)) } } """; // .aspire/modules holds the generated SDK. It is regenerated from the AppHost's integrations // and is large, so it should not be committed. As with apphost.go, the blank line before the closing // quotes is intentional: the file should end with a newline, like go-starter/.gitignore does. files[".gitignore"] = """ .aspire/ """; // Create go.mod with require and replace directives for local modules // Go requires both a `require` directive and a `replace` directive for local modules files["go.mod"] = """ module apphost go 1.26 require apphost/modules/aspire v0.0.0 replace apphost/modules/aspire => ./.aspire/modules """; // Create apphost.run.json with random ports var random = request.PortSeed.HasValue ? new Random(request.PortSeed.Value) : Random.Shared; var ports = AppHostProfilePortGenerator.Generate(random); files["apphost.run.json"] = $$""" { "profiles": { "https": { "applicationUrl": "https://localhost:{{ports.DashboardHttpsPort}};http://localhost:{{ports.DashboardHttpPort}}", "environmentVariables": { "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:{{ports.OtlpHttpsPort}}", "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:{{ports.ResourceServiceHttpsPort}}" } } } } """; // The literals above take the line endings of this source file (CRLF in a Windows checkout), and gofmt requires LF. // Only CRLF is replaced, not every line ending: see AtsGoCodeGenerator.GenerateDistributedApplication for why. return files.ToDictionary(file => file.Key, file => file.Value.Replace("\r\n", "\n")); } /// <inheritdoc /> public DetectionResult Detect(string directoryPath) { var appHostPath = Path.Combine(directoryPath, "apphost.go"); if (!File.Exists(appHostPath)) { return DetectionResult.NotFound; } var goModPath = Path.Combine(directoryPath, "go.mod"); if (!File.Exists(goModPath)) { return DetectionResult.NotFound; } return DetectionResult.Found(LanguageId, "apphost.go"); } /// <inheritdoc /> public RuntimeSpec GetRuntimeSpec() { // Note: InstallDependencies is null because "go run ." handles module // resolution automatically, and InstallDependencies runs BEFORE code // generation which means the .aspire/modules directory doesn't exist yet. return new RuntimeSpec { Language = LanguageId, DisplayName = LanguageDisplayName, CodeGenLanguage = CodeGenTarget, DetectionPatterns = s_detectionPatterns, InstallDependencies = null, // Debugging the AppHost itself goes through the extension's Go debug adapter. The capability is // versioned instead of the plain "go" because extension builds that can only debug Go resources // already advertise "go"; routing apphost.go to them would hand it to the .NET debugger. The CLI // only takes this path when the extension reports the capability, so a CLI-only run is unaffected. ExtensionLaunchCapability = "go-apphost.v1", Execute = new CommandSpec { Command = "go", Args = ["run", "."] } }; } }