Skip to content

Commit 215ae98

Browse files
authored
Start the beginning of AOT'ing the Aspire.Cli. (#9841)
* Start the beginning of AOT'ing the Aspire.Cli. Depends on microsoft/vs-streamjsonrpc#1196 * Revert cli.csproj changes * Revert Designer.cs changes * Share state object types across CLI and Hosting. * Split the Backchannel data types across assemblies again * Revert launchSettings.json
1 parent 54395b2 commit 215ae98

File tree

11 files changed

+197
-110
lines changed

11 files changed

+197
-110
lines changed

Directory.Packages.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@
108108
<PackageVersion Include="StackExchange.Redis" Version="2.8.37" />
109109
<PackageVersion Include="System.IO.Hashing" Version="9.0.4" />
110110
<PackageVersion Include="Yarp.ReverseProxy" Version="2.3.0" />
111-
<PackageVersion Include="StreamJsonRpc" Version="2.21.69" />
111+
<PackageVersion Include="StreamJsonRpc" Version="2.22.11" />
112112
<PackageVersion Include="Semver" Version="3.0.0" />
113113
<!-- Open Telemetry -->
114114
<PackageVersion Include="Npgsql.OpenTelemetry" Version="9.0.3" />

src/Aspire.Cli/Aspire.Cli.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@
3434
</ItemGroup>
3535

3636
<ItemGroup>
37-
<Compile Include="$(RepoRoot)src\Shared\KnownConfigNames.cs" Link="KnownConfigNames.cs" />
37+
<Compile Include="$(SharedDir)KnownConfigNames.cs" Link="KnownConfigNames.cs" />
3838
<Compile Include="$(SharedDir)PathNormalizer.cs" Link="Utils\PathNormalizer.cs" />
3939
<Compile Include="$(SharedDir)CircularBuffer.cs" Link="Utils\CircularBuffer.cs" />
40-
<Compile Include="..\Shared\StringComparers.cs" Link="StringComparers.cs" />
40+
<Compile Include="$(SharedDir)StringComparers.cs" Link="StringComparers.cs" />
4141
</ItemGroup>
4242

4343
<ItemGroup>

src/Aspire.Cli/Backchannel/AppHostBackchannel.cs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ internal interface IAppHostBackchannel
2222
Task<string[]> GetCapabilitiesAsync(CancellationToken cancellationToken);
2323
}
2424

25-
internal sealed class AppHostBackchannel(ILogger<AppHostBackchannel> logger, CliRpcTarget target, AspireCliTelemetry telemetry) : IAppHostBackchannel
25+
internal sealed class AppHostBackchannel(ILogger<AppHostBackchannel> logger, AspireCliTelemetry telemetry) : IAppHostBackchannel
2626
{
2727
private const string BaselineCapability = "baseline.v2";
2828
private readonly TaskCompletionSource<JsonRpc> _rpcTaskCompletionSource = new();
@@ -69,15 +69,15 @@ await rpc.InvokeWithCancellationAsync(
6969

7070
logger.LogDebug("Requesting dashboard URL");
7171

72-
var url = await rpc.InvokeWithCancellationAsync<(string BaseUrlWithLoginToken, string? CodespacesUrlWithLoginToken)>(
72+
var url = await rpc.InvokeWithCancellationAsync<DashboardUrlsState>(
7373
"GetDashboardUrlsAsync",
7474
Array.Empty<object>(),
7575
cancellationToken);
7676

77-
return url;
77+
return (url.BaseUrlWithLoginToken, url.CodespacesUrlWithLoginToken);
7878
}
7979

80-
public async IAsyncEnumerable<RpcResourceState> GetResourceStatesAsync([EnumeratorCancellation]CancellationToken cancellationToken)
80+
public async IAsyncEnumerable<RpcResourceState> GetResourceStatesAsync([EnumeratorCancellation] CancellationToken cancellationToken)
8181
{
8282
using var activity = telemetry.ActivitySource.StartActivity();
8383

@@ -116,7 +116,8 @@ public async Task ConnectAsync(string socketPath, CancellationToken cancellation
116116
logger.LogDebug("Connected to AppHost backchannel at {SocketPath}", socketPath);
117117

118118
var stream = new NetworkStream(socket, true);
119-
var rpc = JsonRpc.Attach(stream, target);
119+
var rpc = new JsonRpc(new HeaderDelimitedMessageHandler(stream, stream, new SystemTextJsonFormatter()));
120+
rpc.StartListening();
120121

121122
var capabilities = await rpc.InvokeWithCancellationAsync<string[]>(
122123
"GetCapabilitiesAsync",
@@ -143,15 +144,15 @@ public async Task ConnectAsync(string socketPath, CancellationToken cancellation
143144
}
144145
}
145146

146-
public async IAsyncEnumerable<(string Id, string StatusText, bool IsComplete, bool IsError)> GetPublishingActivitiesAsync([EnumeratorCancellation]CancellationToken cancellationToken)
147+
public async IAsyncEnumerable<(string Id, string StatusText, bool IsComplete, bool IsError)> GetPublishingActivitiesAsync([EnumeratorCancellation] CancellationToken cancellationToken)
147148
{
148149
using var activity = telemetry.ActivitySource.StartActivity();
149150

150151
var rpc = await _rpcTaskCompletionSource.Task;
151152

152153
logger.LogDebug("Requesting publishing activities.");
153154

154-
var resourceStates = await rpc.InvokeWithCancellationAsync<IAsyncEnumerable<(string Id, string StatusText, bool IsComplete, bool IsError)>>(
155+
var resourceStates = await rpc.InvokeWithCancellationAsync<IAsyncEnumerable<PublishingActivityState>>(
155156
"GetPublishingActivitiesAsync",
156157
Array.Empty<object>(),
157158
cancellationToken);
@@ -160,7 +161,11 @@ public async Task ConnectAsync(string socketPath, CancellationToken cancellation
160161

161162
await foreach (var state in resourceStates.WithCancellation(cancellationToken))
162163
{
163-
yield return state;
164+
yield return (
165+
state.Id,
166+
state.StatusText,
167+
state.IsComplete,
168+
state.IsError);
164169
}
165170
}
166171

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Aspire.Cli.Backchannel;
5+
6+
/// <summary>
7+
/// Represents the state of a resource reported via RPC.
8+
/// </summary>
9+
internal sealed class RpcResourceState
10+
{
11+
/// <summary>
12+
/// Gets the name of the resource.
13+
/// </summary>
14+
public required string Resource { get; init; }
15+
16+
/// <summary>
17+
/// Gets the type of the resource.
18+
/// </summary>
19+
public required string Type { get; init; }
20+
21+
/// <summary>
22+
/// Gets the state of the resource.
23+
/// </summary>
24+
public required string State { get; init; }
25+
26+
/// <summary>
27+
/// Gets the endpoints associated with the resource.
28+
/// </summary>
29+
public required string[] Endpoints { get; init; }
30+
31+
/// <summary>
32+
/// Gets the health status of the resource.
33+
/// </summary>
34+
public string? Health { get; init; }
35+
}
36+
37+
/// <summary>
38+
/// Represents dashboard URLs with authentication tokens.
39+
/// </summary>
40+
internal sealed class DashboardUrlsState
41+
{
42+
/// <summary>
43+
/// Gets the base dashboard URL with a login token.
44+
/// </summary>
45+
public required string BaseUrlWithLoginToken { get; init; }
46+
47+
/// <summary>
48+
/// Gets the Codespaces dashboard URL with a login token, if available.
49+
/// </summary>
50+
public string? CodespacesUrlWithLoginToken { get; init; }
51+
}
52+
53+
/// <summary>
54+
/// Represents the activity and status of a publishing operation.
55+
/// </summary>
56+
internal sealed class PublishingActivityState
57+
{
58+
/// <summary>
59+
/// Gets the unique identifier for the publishing activity.
60+
/// </summary>
61+
public required string Id { get; init; }
62+
63+
/// <summary>
64+
/// Gets the status text describing the publishing activity.
65+
/// </summary>
66+
public required string StatusText { get; init; }
67+
68+
/// <summary>
69+
/// Gets a value indicating whether the publishing activity is complete.
70+
/// </summary>
71+
public bool IsComplete { get; init; }
72+
73+
/// <summary>
74+
/// Gets a value indicating whether the publishing activity encountered an error.
75+
/// </summary>
76+
public bool IsError { get; init; }
77+
}

src/Aspire.Cli/Backchannel/CliRpcTarget.cs

Lines changed: 0 additions & 8 deletions
This file was deleted.

src/Aspire.Cli/Backchannel/RpcResourceState.cs

Lines changed: 0 additions & 35 deletions
This file was deleted.

src/Aspire.Cli/Program.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@ private static IHost BuildApplication(string[] args)
9999
builder.Services.AddSingleton<AspireCliTelemetry>();
100100
builder.Services.AddTransient<IDotNetCliRunner, DotNetCliRunner>();
101101
builder.Services.AddTransient<IAppHostBackchannel, AppHostBackchannel>();
102-
builder.Services.AddSingleton<CliRpcTarget>();
103102
builder.Services.AddSingleton<INuGetPackageCache, NuGetPackageCache>();
104103
builder.Services.AddHostedService(BuildNuGetPackagePrefetcher);
105104
builder.Services.AddMemoryCache();

src/Aspire.Hosting/Backchannel/AppHostRpcTarget.cs

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ internal class AppHostRpcTarget(
2121
PublishingActivityProgressReporter activityReporter,
2222
IHostApplicationLifetime lifetime,
2323
DistributedApplicationOptions options
24-
)
24+
)
2525
{
26-
public async IAsyncEnumerable<(string Id, string StatusText, bool IsComplete, bool IsError)> GetPublishingActivitiesAsync([EnumeratorCancellation]CancellationToken cancellationToken)
26+
public async IAsyncEnumerable<PublishingActivityState> GetPublishingActivitiesAsync([EnumeratorCancellation] CancellationToken cancellationToken)
2727
{
2828
while (cancellationToken.IsCancellationRequested == false)
2929
{
@@ -36,14 +36,15 @@ DistributedApplicationOptions options
3636
yield break;
3737
}
3838

39-
yield return (
40-
publishingActivityStatus.Activity.Id,
41-
publishingActivityStatus.StatusText,
42-
publishingActivityStatus.IsComplete,
43-
publishingActivityStatus.IsError
44-
);
39+
yield return new PublishingActivityState
40+
{
41+
Id = publishingActivityStatus.Activity.Id,
42+
StatusText = publishingActivityStatus.StatusText,
43+
IsComplete = publishingActivityStatus.IsComplete,
44+
IsError = publishingActivityStatus.IsError
45+
};
4546

46-
if ( publishingActivityStatus.Activity.IsPrimary &&(publishingActivityStatus.IsComplete || publishingActivityStatus.IsError))
47+
if (publishingActivityStatus.Activity.IsPrimary && (publishingActivityStatus.IsComplete || publishingActivityStatus.IsError))
4748
{
4849
// If the activity is complete or an error and it is the primary activity,
4950
// we can stop listening for updates.
@@ -52,7 +53,7 @@ DistributedApplicationOptions options
5253
}
5354
}
5455

55-
public async IAsyncEnumerable<RpcResourceState> GetResourceStatesAsync([EnumeratorCancellation]CancellationToken cancellationToken)
56+
public async IAsyncEnumerable<RpcResourceState> GetResourceStatesAsync([EnumeratorCancellation] CancellationToken cancellationToken)
5657
{
5758
var resourceEvents = resourceNotificationService.WatchAsync(cancellationToken);
5859

@@ -69,15 +70,15 @@ public async IAsyncEnumerable<RpcResourceState> GetResourceStatesAsync([Enumerat
6970
logger.LogTrace("Resource {Resource} does not have endpoints.", resourceEvent.Resource.Name);
7071
endpoints = Enumerable.Empty<EndpointAnnotation>();
7172
}
72-
73+
7374
var endpointUris = endpoints
7475
.Where(e => e.AllocatedEndpoint != null)
7576
.Select(e => e.AllocatedEndpoint!.UriString)
7677
.ToArray();
7778

7879
// Compute health status
7980
var healthStatus = CustomResourceSnapshot.ComputeHealthStatus(resourceEvent.Snapshot.HealthReports, resourceEvent.Snapshot.State?.Text);
80-
81+
8182
yield return new RpcResourceState
8283
{
8384
Resource = resourceEvent.Resource.Name,
@@ -103,12 +104,12 @@ public Task<long> PingAsync(long timestamp, CancellationToken cancellationToken)
103104
return Task.FromResult(timestamp);
104105
}
105106

106-
public Task<(string BaseUrlWithLoginToken, string? CodespacesUrlWithLoginToken)> GetDashboardUrlsAsync()
107+
public Task<DashboardUrlsState> GetDashboardUrlsAsync()
107108
{
108109
return GetDashboardUrlsAsync(CancellationToken.None);
109110
}
110111

111-
public async Task<(string BaseUrlWithLoginToken, string? CodespacesUrlWithLoginToken)> GetDashboardUrlsAsync(CancellationToken cancellationToken)
112+
public async Task<DashboardUrlsState> GetDashboardUrlsAsync(CancellationToken cancellationToken)
112113
{
113114
if (!options.DashboardEnabled)
114115
{
@@ -134,7 +135,7 @@ await resourceNotificationService.WaitForResourceHealthyAsync(
134135
if (!StringUtils.TryGetUriFromDelimitedString(dashboardOptions.Value.DashboardUrl, ";", out var dashboardUri))
135136
{
136137
logger.LogWarning("Dashboard URL could not be parsed from dashboard options.");
137-
throw new InvalidOperationException("Dashboard URL could not be parsed from dashboard options.");
138+
throw new InvalidOperationException("Dashboard URL could not be parsed from dashboard options.");
138139
}
139140

140141
var codespacesUrlRewriter = serviceProvider.GetService<CodespacesUrlRewriter>();
@@ -144,11 +145,18 @@ await resourceNotificationService.WaitForResourceHealthyAsync(
144145

145146
if (baseUrlWithLoginToken == codespacesUrlWithLoginToken)
146147
{
147-
return (baseUrlWithLoginToken, null);
148+
return new DashboardUrlsState
149+
{
150+
BaseUrlWithLoginToken = baseUrlWithLoginToken
151+
};
148152
}
149153
else
150154
{
151-
return (baseUrlWithLoginToken, codespacesUrlWithLoginToken);
155+
return new DashboardUrlsState
156+
{
157+
BaseUrlWithLoginToken = baseUrlWithLoginToken,
158+
CodespacesUrlWithLoginToken = codespacesUrlWithLoginToken
159+
};
152160
}
153161
}
154162

@@ -178,4 +186,4 @@ public Task<string[]> GetCapabilitiesAsync(CancellationToken cancellationToken)
178186
});
179187
}
180188
#pragma warning restore CA1822
181-
}
189+
}

0 commit comments

Comments
 (0)