Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
678bc81
Fix MAUI iOS simulator VS Code launch
adamint Jun 2, 2026
b3fa1b0
Avoid MAUI launch protocol changes
adamint Jun 2, 2026
d62a003
Share dotnet run launch setup
adamint Jun 2, 2026
63b2e41
Simplify dotnet run argument helpers
adamint Jun 2, 2026
c9d0548
Fix MAUI iOS simulator process launch
adamint Jun 3, 2026
f16215e
Preserve AppHost dotnet run options for custom project executables
adamint Jun 15, 2026
976352a
Keep MAUI launch profile args after dotnet run
adamint Jun 15, 2026
1605937
Merge main into MAUI iOS simulator PR
adamint Jun 15, 2026
4fda000
Address launch args review feedback
adamint Jun 17, 2026
b41a9ae
Support MAUI debug launch metadata
adamint Jun 19, 2026
53e42df
Merge remote-tracking branch 'upstream/main' into fleet/pr-lane-vscod…
adamint Jun 19, 2026
7f6fc1e
Fix MAUI debug merge with upstream extension API
adamint Jun 19, 2026
16ca6ff
Cover missing Android physical device target
adamint Jun 19, 2026
1ef71f7
Let MAUI launch stopped Android emulators
adamint Jun 19, 2026
a34a5b8
Include MAUI target in debug session name
adamint Jun 19, 2026
08962bc
Harden MAUI debug session logging and disposal
adamint Jun 19, 2026
020439e
Fix MAUI debug review and CI regressions
adamint Jun 19, 2026
10b61f9
Fix E2E integrated browser endpoint result
adamint Jun 20, 2026
3f76df4
Adopt AppHost termination refresh hook
adamint Jun 20, 2026
4cabd8d
Merge main into MAUI iOS simulator PR
adamint Jun 20, 2026
66b8156
Remove dead MAUI device flag
adamint Jun 20, 2026
f228ad5
Restore E2E publish AppHost bridge
adamint Jun 20, 2026
fff6385
Fix explicit Aspire repo detection cache
adamint Jun 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 35 additions & 14 deletions extension/src/debugger/languages/dotnet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import * as readline from 'readline';
import * as os from 'os';
import * as fs from 'fs';
import { doesFileExist } from '../../utils/io';
import { AspireResourceExtendedDebugConfiguration, ExecutableLaunchConfiguration, isProjectLaunchConfiguration, ProjectLaunchConfiguration } from '../../dcp/types';
import { AspireResourceExtendedDebugConfiguration, EnvVar, ExecutableLaunchConfiguration, isProjectLaunchConfiguration, ProjectLaunchConfiguration } from '../../dcp/types';
import { ResourceDebuggerExtension } from '../debuggerExtensions';
import {
readLaunchSettings,
Expand Down Expand Up @@ -232,8 +232,12 @@ function quoteCommandLineArgument(argument: string): string {
return `"${argument.replace(/"/g, '\\"')}"`;
}

function createDotNetRunBaseArguments(projectPath: string): string[] {
return ['run', '--project', projectPath, '--no-launch-profile'];
}

function createDotNetRunArguments(projectPath: string, baseProfileArgs: string | undefined, runSessionArgs: string[] | undefined): string[] | string {
const dotnetRunArgs = ['run', '--project', projectPath, '--no-launch-profile'];
const dotnetRunArgs = createDotNetRunBaseArguments(projectPath);
if (runSessionArgs !== undefined) {
if (runSessionArgs.length > 0) {
dotnetRunArgs.push('--', ...runSessionArgs);
Expand All @@ -253,6 +257,24 @@ function createDotNetRunArguments(projectPath: string, baseProfileArgs: string |
return dotnetRunArgs;
}

function configureDotNetRunDebugConfiguration(
debugConfiguration: AspireResourceExtendedDebugConfiguration,
projectPath: string,
args: string[] | string,
baseProfileEnvironmentVariables: { [key: string]: string } | undefined,
runSessionEnvironmentVariables: EnvVar[]): void {
debugConfiguration.program = 'dotnet';
debugConfiguration.args = args;
debugConfiguration.cwd = path.dirname(projectPath);
debugConfiguration.executablePath = undefined;
debugConfiguration.noDebug = true;
debugConfiguration.env = Object.fromEntries(mergeEnvironmentVariables(
baseProfileEnvironmentVariables,
debugConfiguration.env,
runSessionEnvironmentVariables
));
}

export function createProjectDebuggerExtension(dotNetServiceProducer: (debugSession: AspireDebugSession) => IDotNetService): ResourceDebuggerExtension {
return {
resourceType: 'project',
Expand All @@ -268,8 +290,6 @@ export function createProjectDebuggerExtension(dotNetServiceProducer: (debugSess
throw new Error(invalidLaunchConfiguration(JSON.stringify(launchConfig)));
},
createDebugSessionConfigurationCallback: async (launchConfig, args, env, launchOptions, debugConfiguration: AspireResourceExtendedDebugConfiguration): Promise<void> => {
const dotNetService: IDotNetService = dotNetServiceProducer(launchOptions.debugSession);

if (!isProjectLaunchConfiguration(launchConfig)) {
extensionLogOutputChannel.info(`The resource type was not project for ${JSON.stringify(launchConfig)}`);
throw new Error(invalidLaunchConfiguration(JSON.stringify(launchConfig)));
Expand Down Expand Up @@ -318,6 +338,8 @@ export function createProjectDebuggerExtension(dotNetServiceProducer: (debugSess
}

if (baseProfile?.commandName?.toLowerCase() === LaunchProfileCommandName.executable && baseProfile.executablePath) {
const dotNetService: IDotNetService = dotNetServiceProducer(launchOptions.debugSession);

// For Executable command profiles (e.g., class library integrations), the launch profile
// specifies an external executable to run instead of the project output.
// Build the project to ensure dependencies are compiled, then launch
Expand All @@ -340,6 +362,7 @@ export function createProjectDebuggerExtension(dotNetServiceProducer: (debugSess
));
}
else if (!isFileBasedApp(projectPath)) {
const dotNetService: IDotNetService = dotNetServiceProducer(launchOptions.debugSession);
const outputPath = await dotNetService.getDotNetTargetPath(projectPath);
if ((!(await doesFileExist(outputPath)) || launchOptions.forceBuild)) {
await dotNetService.buildDotNetProject(projectPath);
Expand All @@ -352,21 +375,19 @@ export function createProjectDebuggerExtension(dotNetServiceProducer: (debugSess
vscode.window.showInformationMessage(fallbackMessage);
}

debugConfiguration.program = 'dotnet';
debugConfiguration.args = createDotNetRunArguments(projectPath, baseProfile?.commandLineArgs, args);
debugConfiguration.cwd = path.dirname(projectPath);
debugConfiguration.executablePath = undefined;
debugConfiguration.noDebug = true;
configureDotNetRunDebugConfiguration(debugConfiguration, projectPath, createDotNetRunArguments(projectPath, baseProfile?.commandLineArgs, args), baseProfile?.environmentVariables, env);
} else {
debugConfiguration.program = outputPath;
debugConfiguration.env = Object.fromEntries(mergeEnvironmentVariables(
baseProfile?.environmentVariables,
debugConfiguration.env,
env
));
}
debugConfiguration.env = Object.fromEntries(mergeEnvironmentVariables(
baseProfile?.environmentVariables,
debugConfiguration.env,
env
));
}
else {
const dotNetService: IDotNetService = dotNetServiceProducer(launchOptions.debugSession);

// For file-based apps, get the dotnet run-api output first to determine the executable path
const runApiOutput = await dotNetService.getDotNetRunApiOutput(projectPath);
const runApiConfig = getRunApiConfigFromOutput(runApiOutput);
Expand Down
28 changes: 28 additions & 0 deletions extension/src/test/dotnetDebugger.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,34 @@ suite('Dotnet Debugger Extension Tests', () => {
assert.strictEqual(dotNetService.buildDotNetProjectStub.notCalled, true);
});

test('does not use dotnet run when ordinary project launch configuration requests NoDebug', async () => {
const outputPath = '/tmp/bin/Debug/net10.0/Worker.dll';
const { extension } = createDebuggerExtension(outputPath, null, true, true);

const projectPath = '/tmp/Worker.csproj';
const launchConfig: ProjectLaunchConfiguration = {
type: 'project',
mode: 'NoDebug',
project_path: projectPath
};

const debugConfig: AspireResourceExtendedDebugConfiguration = {
runId: '1',
debugSessionId: '1',
type: 'coreclr',
name: 'Test Debug Config',
request: 'launch',
noDebug: true
};

const fakeAspireDebugSession = sinon.createStubInstance(AspireDebugSession);

await extension.createDebugSessionConfigurationCallback!(launchConfig, [], [], { debug: false, runId: '1', debugSessionId: '1', isApphost: false, debugSession: fakeAspireDebugSession }, debugConfig);

assert.strictEqual(debugConfig.program, outputPath);
assert.strictEqual(debugConfig.noDebug, true);
});

test('uses dotnet CLI when project runtimeconfig has no runnable framework', async () => {
const fs = require('fs');
const path = require('path');
Expand Down
8 changes: 7 additions & 1 deletion src/Aspire.Hosting.Maui/MauiiOSExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -360,14 +360,20 @@ public static IResourceBuilder<MauiiOSSimulatorResource> AddiOSSimulator(

var iOSSimulatorResource = new MauiiOSSimulatorResource(name, builder.Resource);

#pragma warning disable ASPIREEXTENSION001 // WithDebugSupport is experimental
var resourceBuilder = builder.ApplicationBuilder.AddResource(iOSSimulatorResource)
.WithAnnotation(new MauiProjectMetadata(projectPath))
.WithAnnotation(new MauiiOSEnvironmentAnnotation()) // Enable environment variable support via targets file
.WithAnnotation(new ExecutableAnnotation
{
Command = "dotnet",
WorkingDirectory = workingDirectory
});
})
// VS Code does not advertise this launch type, so DCP runs the simulator
// resource as the executable below. That keeps the MAUI SDK `dotnet run`
// target in control instead of launching the compiled DLL directly.
.WithDebugSupport(_ => new { type = "maui-ios-simulator" }, "maui-ios-simulator");
#pragma warning restore ASPIREEXTENSION001

// Build additional arguments for simulator UDID if specified
// For iOS simulators, we need to use the MSBuild property _DeviceName with the :v2:udid= prefix
Expand Down
Loading