Skip to content

Commit 074290c

Browse files
committed
don't build apphost in cli when running in extension
1 parent 7186f68 commit 074290c

File tree

2 files changed

+71
-21
lines changed

2 files changed

+71
-21
lines changed

src/Aspire.Cli/Commands/RunCommand.cs

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -138,31 +138,25 @@ protected override async Task<int> ExecuteAsync(ParseResult parseResult, Cancell
138138

139139
await _certificateService.EnsureCertificatesTrustedAsync(_runner, cancellationToken);
140140

141-
var shouldBuildAppHostInExtension = await ShouldBuildAppHostInExtensionAsync(InteractionService, isSingleFileAppHost, cancellationToken);
142-
143141
var watch = !isSingleFileAppHost && (_features.IsFeatureEnabled(KnownFeatures.DefaultWatchEnabled, defaultValue: false) || (isExtensionHost && !startDebugSession));
144142

145143
if (!watch)
146144
{
147-
if (!isSingleFileAppHost || isExtensionHost)
145+
if (!isSingleFileAppHost && !isExtensionHost)
148146
{
149147
var buildOptions = new DotNetCliRunnerInvocationOptions
150148
{
151149
StandardOutputCallback = buildOutputCollector.AppendOutput,
152150
StandardErrorCallback = buildOutputCollector.AppendError,
153151
};
154152

155-
// The extension host will build the app host project itself, so we don't need to do it here if host exists.
156-
if (!shouldBuildAppHostInExtension)
157-
{
158-
var buildExitCode = await AppHostHelper.BuildAppHostAsync(_runner, InteractionService, effectiveAppHostFile, buildOptions, ExecutionContext.WorkingDirectory, cancellationToken);
153+
var buildExitCode = await AppHostHelper.BuildAppHostAsync(_runner, InteractionService, effectiveAppHostFile, buildOptions, ExecutionContext.WorkingDirectory, cancellationToken);
159154

160-
if (buildExitCode != 0)
161-
{
162-
InteractionService.DisplayLines(buildOutputCollector.GetLines());
163-
InteractionService.DisplayError(InteractionServiceStrings.ProjectCouldNotBeBuilt);
164-
return ExitCodeConstants.FailedToBuildArtifacts;
165-
}
155+
if (buildExitCode != 0)
156+
{
157+
InteractionService.DisplayLines(buildOutputCollector.GetLines());
158+
InteractionService.DisplayError(InteractionServiceStrings.ProjectCouldNotBeBuilt);
159+
return ExitCodeConstants.FailedToBuildArtifacts;
166160
}
167161
}
168162
}
@@ -224,7 +218,7 @@ protected override async Task<int> ExecuteAsync(ParseResult parseResult, Cancell
224218
cancellationToken);
225219

226220
// Wait for the backchannel to be established.
227-
var backchannel = await InteractionService.ShowStatusAsync(shouldBuildAppHostInExtension ? InteractionServiceStrings.BuildingAppHost : RunCommandStrings.ConnectingToAppHost, async () => { return await backchannelCompletitionSource.Task.WaitAsync(cancellationToken); });
221+
var backchannel = await InteractionService.ShowStatusAsync(InteractionServiceStrings.BuildingAppHost, async () => { return await backchannelCompletitionSource.Task.WaitAsync(cancellationToken); });
228222

229223
var logFile = GetAppHostLogFile();
230224

@@ -491,11 +485,4 @@ public void ProcessResourceState(RpcResourceState resourceState, Action<string,
491485
_resourceStates[resourceState.Resource] = resourceState;
492486
}
493487
}
494-
495-
private static async Task<bool> ShouldBuildAppHostInExtensionAsync(IInteractionService interactionService, bool isSingleFileAppHost, CancellationToken cancellationToken)
496-
{
497-
return ExtensionHelper.IsExtensionHost(interactionService, out _, out var extensionBackchannel)
498-
&& await extensionBackchannel.HasCapabilityAsync(KnownCapabilities.DevKit, cancellationToken)
499-
&& !isSingleFileAppHost;
500-
}
501488
}

tests/Aspire.Cli.Tests/Commands/RunCommandTests.cs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,69 @@ public async Task RunCommand_SkipsBuild_WhenExtensionDevKitCapabilityIsAvailable
491491
Assert.False(buildCalled, "Build should be skipped when extension DevKit capability is available.");
492492
}
493493

494+
[Fact]
495+
public async Task RunCommand_SkipsBuild_WhenRunningInExtension()
496+
{
497+
var buildCalled = false;
498+
499+
var extensionBackchannel = new TestExtensionBackchannel();
500+
extensionBackchannel.GetCapabilitiesAsyncCallback = ct => Task.FromResult(Array.Empty<string>());
501+
502+
var appHostBackchannel = new TestAppHostBackchannel();
503+
appHostBackchannel.GetDashboardUrlsAsyncCallback = (ct) => Task.FromResult(new DashboardUrlsState
504+
{
505+
DashboardHealthy = true,
506+
BaseUrlWithLoginToken = "http://localhost/dashboard",
507+
CodespacesUrlWithLoginToken = null
508+
});
509+
appHostBackchannel.GetAppHostLogEntriesAsyncCallback = ReturnLogEntriesUntilCancelledAsync;
510+
511+
var backchannelFactory = (IServiceProvider sp) => appHostBackchannel;
512+
513+
var extensionInteractionServiceFactory = (IServiceProvider sp) => new TestExtensionInteractionService(sp);
514+
515+
var runnerFactory = (IServiceProvider sp) => {
516+
var runner = new TestDotNetCliRunner();
517+
runner.CheckHttpCertificateAsyncCallback = (options, ct) => 0;
518+
runner.BuildAsyncCallback = (projectFile, options, ct) => {
519+
buildCalled = true;
520+
return 0;
521+
};
522+
runner.GetAppHostInformationAsyncCallback = (projectFile, options, ct) => (0, true, VersionHelper.GetDefaultTemplateVersion());
523+
runner.RunAsyncCallback = async (projectFile, watch, noBuild, args, env, backchannelCompletionSource, options, ct) => {
524+
var backchannel = sp.GetRequiredService<IAppHostBackchannel>();
525+
backchannelCompletionSource!.SetResult(backchannel);
526+
await Task.Delay(Timeout.InfiniteTimeSpan, ct);
527+
return 0;
528+
};
529+
return runner;
530+
};
531+
532+
var projectLocatorFactory = (IServiceProvider sp) => new TestProjectLocator();
533+
534+
using var workspace = TemporaryWorkspace.Create(outputHelper);
535+
var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
536+
{
537+
options.ProjectLocatorFactory = projectLocatorFactory;
538+
options.AppHostBackchannelFactory = backchannelFactory;
539+
options.DotNetCliRunnerFactory = runnerFactory;
540+
options.ExtensionBackchannelFactory = _ => extensionBackchannel;
541+
options.InteractionServiceFactory = extensionInteractionServiceFactory;
542+
});
543+
544+
var provider = services.BuildServiceProvider();
545+
var command = provider.GetRequiredService<RootCommand>();
546+
var result = command.Parse("run");
547+
548+
using var cts = new CancellationTokenSource();
549+
var pendingRun = result.InvokeAsync(cancellationToken: cts.Token);
550+
cts.Cancel();
551+
var exitCode = await pendingRun.WaitAsync(CliTestConstants.DefaultTimeout);
552+
553+
Assert.Equal(ExitCodeConstants.Success, exitCode);
554+
Assert.False(buildCalled, "Build should be skipped when running in extension.");
555+
}
556+
494557
[Fact]
495558
public async Task RunCommand_WhenSingleFileAppHostAndDefaultWatchEnabled_DoesNotUseWatchMode()
496559
{

0 commit comments

Comments
 (0)