Skip to content

Add better WithDebugSupport extensibility api#12711

Closed
adamint wants to merge 81 commits intodotnet:mainfrom
adamint:dev/adamint/extensible-debug-support
Closed

Add better WithDebugSupport extensibility api#12711
adamint wants to merge 81 commits intodotnet:mainfrom
adamint:dev/adamint/extensible-debug-support

Conversation

@adamint
Copy link
Member

@adamint adamint commented Nov 5, 2025

Description

This PR adds extensible debug support APIs to Aspire, allowing any IDE to plug in their own debug configuration by extending a common base. The debug infrastructure types are kept internal while exposing a minimal, IDE-agnostic public API surface.

Public API Surface (all marked [Experimental("ASPIREEXTENSION001")])

Aspire.Hosting:

  • WithDebugging<T>(builder, string projectPath) where T : ProjectResource — Configures debugging for project resources (only needed for custom ProjectResource inheritors)

Aspire.Hosting.JavaScript:

  • WithDebugging<T>(builder, string scriptPath) where T : NodeAppResource — Configures debugging for Node.js apps with a script path
  • WithDebugging<T>(builder) where T : JavaScriptAppResource — Configures debugging for JavaScript apps using package manager scripts
  • WithBrowserDebugger<T>(builder, string browser = "msedge") where T : JavaScriptAppResource — Configures browser debugging by creating a child browser debugger resource

Aspire.Hosting.Python:

  • WithDebugging<T>(builder) where T : PythonAppResource — Configures debugging for Python apps

Internal Infrastructure

All debug property classes, launch configuration types, and infrastructure methods are internal:

  • WithVSCodeDebugging (all packages) — now internal, delegated to by public WithDebugging
  • WithDebugSupport, WithDebuggerProperties, WithVSCodeDebugOptions
  • Per-language WithVSCode*DebuggerProperties methods
  • All types: ExecutableLaunchConfiguration, DebugAdapterProperties, VSCodeDebuggerPropertiesBase, SupportsDebuggingAnnotation, LaunchConfigurationProducerOptions, ProjectLaunchConfiguration, NodeLaunchConfiguration, BrowserLaunchConfiguration, etc.

Cross-assembly access is enabled via InternalsVisibleTo.

Key Design Concepts

  • DebugAdapterProperties — abstract base for DAP-standard properties any IDE can use
  • VSCodeDebuggerPropertiesBase — adds VS Code-specific options (presentation, preLaunchTask, serverReadyAction)
  • IDE filtering via AspireIde class and ExecutableDebuggerPropertiesAnnotation — annotations only fire when the matching IDE is running
  • Polymorphic JSON via DebugAdapterPropertiesJsonConverter for derived type serialization
  • IDE-agnostic public API — public methods are named WithDebugging/WithBrowserDebugger (not WithVSCodeDebugging) so the API is stable across IDE implementations

Summary of Changes

  • Made all debug property classes internal across Aspire.Hosting, Aspire.Hosting.JavaScript, and Aspire.Hosting.Python
  • Made all debug infrastructure methods internal (including WithVSCodeDebugging)
  • Added public WithDebugging methods across all packages that delegate to internal WithVSCodeDebugging
  • Kept public WithBrowserDebugger for JavaScript resources
  • Python's WithDebugging is no longer [Obsolete] — it is now the canonical public API
  • Added InternalsVisibleTo for cross-assembly access between hosting packages
  • Removed redundant shared file includes (now accessed via InternalsVisibleTo)
  • Fixed duplicate LaunchConfigurationAnnotator call in DcpExecutor.PreparePlainExecutables
  • Updated playground projects to use public APIs only

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks> and <code/> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
    • No
  • Does the change require an update in our Aspire docs?
    • Yes
    • No

@github-actions
Copy link
Contributor

github-actions bot commented Nov 5, 2025

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 12711

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 12711"

@adamint adamint marked this pull request as ready for review November 6, 2025 07:17
@adamint adamint requested a review from mitchdenny as a code owner November 6, 2025 07:17
Copilot AI review requested due to automatic review settings November 6, 2025 07:17
@adamint adamint self-assigned this Nov 6, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR refactors the debugging support infrastructure for Aspire resources by introducing a more structured approach to configuring debugger properties. The main purpose is to enable customizable debugging experiences for Python and other resources by allowing developers to configure debugger-specific properties (like stop-on-entry, auto-reload, etc.) through a strongly-typed API.

Key changes:

  • Introduces DebuggerProperties base class and ExecutableLaunchConfigurationWithDebuggerProperties<T> for type-safe debugger configuration
  • Adds new WithDebuggerProperties and WithPythonDebuggerProperties extension methods for configuring debug settings
  • Refactors launch configuration producer to pass LaunchConfigurationProducerOptions instead of just a string mode
  • Moves shared debug support logic from multiple locations into ResourceDebugSupportExtensions.cs

Reviewed Changes

Copilot reviewed 25 out of 25 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/Aspire.Hosting/ExecutableLaunchConfiguration.cs New file defining base classes for launch configurations with debugger properties support
src/Aspire.Hosting/ExecutableDebuggerPropertiesAnnotation.cs New annotation for attaching debugger configuration to resources
src/Aspire.Hosting/SupportsDebuggingAnnotation.cs Changed visibility to public, updated to use LaunchConfigurationProducerOptions
src/Aspire.Hosting/ResourceBuilderExtensions.cs Moved WithDebugSupport and added new WithDebuggerProperties extension method
src/Aspire.Hosting.Python/PythonAppLaunchConfiguration.cs Extended to include full Python debugger properties and configuration options
src/Aspire.Hosting.Python/PythonAppResourceBuilderExtensions.cs Updated to use new debugger properties infrastructure and added WithPythonDebuggerProperties
src/Shared/ResourceDebugSupportExtensions.cs New shared file consolidating debug support logic previously scattered across files
src/Aspire.Hosting/Dcp/DcpExecutor.cs Updated to inject and use IBackchannelLoggerProvider for debug console logging
src/Aspire.Hosting/Backchannel/BackchannelLoggerProvider.cs Added IBackchannelLoggerProvider interface for testability
tests/* Updated tests to use CustomExecutableLaunchConfiguration and new API signatures
extension/* TypeScript changes to support debugger_properties in launch configurations

adamint and others added 4 commits November 6, 2025 15:42
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@adamint adamint marked this pull request as draft November 7, 2025 02:43
cliAvailableOnPath = true;
return true;
} catch (error) {
extensionLogOutputChannel.error(`Aspire CLI not found at path: ${cliPath}. Error: ${error}`);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not strictly related


// General 404 handler (for non-API routes)
app.use('*', (req, res) => {
app.use((req, res) => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the framework changed and this is required

@adamint
Copy link
Member Author

adamint commented Feb 5, 2026

@davidfowl

/// be extended to map to the properties made available by any VS Code debug adapter.
/// </summary>
[Experimental("ASPIREEXTENSION001", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")]
public abstract class VSCodeDebuggerProperties
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider DebugAdapterProperties as a more IDE agnostic name.

Suggested change
public abstract class VSCodeDebuggerProperties
public abstract class DebugAdapterProperties

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, thinking about this a bit more; I think it makes sense to make the VSCode specific properties a separate (or child) section of the debug properties. This type is currently effectively the standard debug adapter protocol launch/attach configuration with VSCode specific types mixed in. We can extend this pattern to support VS (and other) editors if we treat the IDE specific config as a distinct payload.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The actual DAP specific configuration is effectively determined by the type, request, and (for launch requests) the noDebug property. Everything else is VSCode specific.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Effectively that'd mean splitting out the "how do I debug this thing" DAP specific properties from the VSCode specific config.

/// The label of a task to launch before the start of a debug session. Can be set to ${defaultBuildTask} to use the default build task.
/// </summary>
[JsonPropertyName("preLaunchTask")]
public string? PreLaunchTask { get; set; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Philosophically, I wonder if we need something to solve what vscode pre-launch and post-launch tasks solve directly in Aspire. Ideally Aspire itself would know everything required to build the project for debug.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. Pre-launch tasks are already supported in the existing application model using resource dependencies, and post-launch tasks are supported using WaitFor

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ie, the existing JS hosting extension has a pre-launch 'build' task

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the idea of steering users towards using idiomatic Aspire patterns if possible, but I can understand that there are users who will want to do things the VSCode way if they needed to convert an existing app to use Aspire, so I think these do probably make sense to include.

/// Controls how the debug configuration is displayed in the UI.
/// </summary>
[JsonPropertyName("presentation")]
public PresentationOptions? Presentation { get; set; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the idea that we would have an Aspire specific sidebar to display service debugging sessions? I'd want to avoid surfacing service specific launch targets in the main VSCode debug launch UI to avoid confusion with the core Aspire launch target.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maddymontaquila and I were just talking about showing running apphosts in UI. I think it's a good idea, and this property could be used to control ordering visually. However, presentation is a property supported by VS Code launch configurations (https://code.visualstudio.com/docs/debugtest/debugging-configuration), so I included it here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd argue for omitting any properties we don't HAVE to support for services, just to minimize the initial footprint. Better to add them progressively once we have a reason to support them.

/// Possible values: "neverOpen", "openOnSessionStart", "openOnFirstSessionStart".
/// </summary>
[JsonPropertyName("internalConsoleOptions")]
public string? InternalConsoleOptions { get; set; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense to include this for now, but in the future once DCP implements a DAP client, I expect this'll eventually be overridden to allow DCP to drive and forward PTTY sessions for debugged services to the dashboard.

/// Allows you to connect to a specified port instead of launching the debug adapter.
/// </summary>
[JsonPropertyName("debugServer")]
public int? DebugServer { get; set; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is effectively debug adapter config and isn't unique to VSCode, particularly once we introduce our own debug adapter protocol implementation. Either the extension or the app model will need to know how to actually start the individual debug adapters and whether they communicate over stdio, TCP, etc.

@adamint adamint requested a review from danegsta February 18, 2026 14:26
"description": "Enable or disable the 'aspire exec' command for executing commands inside running resources",
"default": false
},
"experimentalPolyglot:go": {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

auto-generated

@joperezr joperezr added this to the 13.2 milestone Feb 25, 2026
adamint added a commit to adamint/aspire that referenced this pull request Feb 25, 2026
… to release/13.2)

Reworks the debug support API so it's not tied to VS Code. Lets any IDE plug in
their own debug configuration by extending a common base.

Key changes:
- New class hierarchy: DebugAdapterProperties -> VSCodeDebuggerPropertiesBase -> concrete classes
- IDE detection via AspireIde class and ASPIRE_IDE env var
- Filtered annotations with optional ideType parameter
- Polymorphic JSON serialization for derived types
- VS Code extension simplified to spread debugger_properties directly
_innerBuilder.Services.AddSingleton(TimeProvider.System);

_innerBuilder.Services.AddSingleton<ILoggerProvider, BackchannelLoggerProvider>();
_innerBuilder.Services.AddSingleton<IBackchannelLoggerProvider, BackchannelLoggerProvider>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this technically a breaking change? I thought logger infrastructure resolves IEnumerable to find log providers, so does this mean this one will now get missed? I would have expected something like:

   _innerBuilder.Services.AddSingleton<BackchannelLoggerProvider>();
   _innerBuilder.Services.AddSingleton<ILoggerProvider>(sp => sp.GetRequiredService<BackchannelLoggerProvider>());
   _innerBuilder.Services.AddSingleton<IBackchannelLoggerProvider>(sp => sp.GetRequiredService<BackchannelLoggerProvider>());


if (!configuration) {
extensionLogOutputChannel.error(`Failed to create debug configuration for ${JSON.stringify(launchConfig)} - resulting configuration was undefined or empty.`);
throw new Error(invalidLaunchConfiguration(JSON.stringify(configuration)));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be launchConfig instead of configuration?

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@adamint adamint closed this Mar 2, 2026
@dotnet-policy-service dotnet-policy-service bot modified the milestones: 13.2, 13.3 Mar 2, 2026
adamint added a commit that referenced this pull request Mar 4, 2026
* Add better WithDebugSupport extensibility API (backport of #12711 to release/13.2)

Reworks the debug support API so it's not tied to VS Code. Lets any IDE plug in
their own debug configuration by extending a common base.

Key changes:
- New class hierarchy: DebugAdapterProperties -> VSCodeDebuggerPropertiesBase -> concrete classes
- IDE detection via AspireIde class and ASPIRE_IDE env var
- Filtered annotations with optional ideType parameter
- Polymorphic JSON serialization for derived types
- VS Code extension simplified to spread debugger_properties directly

* Fix BackchannelLoggerProvider DI registration and clear launch configs on restart

- Register BackchannelLoggerProvider as both ILoggerProvider and
  IBackchannelLoggerProvider so the logging pipeline can discover it.
- Clear launch configurations annotation before re-annotating in
  CreateExecutableAsync to prevent stale configs (with null
  debugger_properties due to abstract deserialization) from accumulating
  on resource restart.

* Make WithVSCodeDebugging internal, add public WithDebugging/WithBrowserDebugger

- Made WithVSCodeDebugging internal across all packages (Hosting, JavaScript, Python)
- Added public WithDebugging methods that delegate to internal WithVSCodeDebugging:
  - WithDebugging<T>(builder, projectPath) for ProjectResource
  - WithDebugging<T>(builder, scriptPath) for NodeAppResource
  - WithDebugging<T>(builder) for JavaScriptAppResource
  - WithDebugging<T>(builder) for PythonAppResource
- WithBrowserDebugger<T>(builder, browser) remains public for JavaScriptAppResource
- Python WithDebugging is no longer [Obsolete], now marked [Experimental]
- All underlying types/infrastructure remain internal
- Removed playground calls to now-internal methods

* Make WithVSCodeCSharpDebuggerProperties, WithVSCodeNodeDebuggerProperties, WithVSCodePythonDebuggerProperties internal; split WithBrowserDebugger into public (no config) + internal (with config)

* Fix JavaScript hosting: make WithVSCodeNodeDebuggerProperties internal, split WithBrowserDebugger into public/internal overloads

* Make all debug infrastructure types internal, fix csproj IVT entries and shared file includes

* Simplify debug infrastructure and add tests

- Fix Path.GetDirectoryName null-bypass with safe fallback
- Move JS debugger property defaults (SkipFiles, SourceMaps, AutoAttachChildProcesses) to field initializers
- Fix empty Python interpreter path: fallback to 'python' with warning
- Add 11 new tests across Hosting, JavaScript, and Python debug tests
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants