Add rebuild command and change detection for project resources#15133
Add rebuild command and change detection for project resources#15133mitchdenny merged 35 commits intorelease/13.2from
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 15133Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 15133" |
There was a problem hiding this comment.
Pull request overview
This PR adds a rebuild workflow for .NET ProjectResource instances (via a hidden rebuilder executable + dashboard command) and introduces an opt-in background service that detects source changes and notifies users that a rebuild is needed.
Changes:
- Adds a
rebuildlifecycle command forProjectResourcethat stops the resource, runsdotnet build, forwards build logs, and restarts on success. - Introduces
ProjectChangeDetectionService+ supporting build/closure utilities to periodically detect file changes and notify via resource logs + dashboard notification (opt-in). - Adds localized command strings and test coverage for the new command and closure/build helper behavior.
Reviewed changes
Copilot reviewed 25 out of 26 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Aspire.Hosting.Tests/ResourceCommandAnnotationTests.cs | Adds rebuild command state/description tests and verifies it’s only on project resources. |
| tests/Aspire.Hosting.Tests/Build/ProjectFileClosureTests.cs | Adds tests for detecting changed files via captured timestamps. |
| tests/Aspire.Hosting.Tests/Build/ProjectBuildHelperTests.cs | Adds basic tests around GetProjectFileClosureAsync error/cancellation behavior. |
| src/Aspire.Hosting/Resources/xlf/CommandStrings.zh-Hant.xlf | Adds Rebuild localized string entries (new/untranslated). |
| src/Aspire.Hosting/Resources/xlf/CommandStrings.zh-Hans.xlf | Adds Rebuild localized string entries (new/untranslated). |
| src/Aspire.Hosting/Resources/xlf/CommandStrings.tr.xlf | Adds Rebuild localized string entries (new/untranslated). |
| src/Aspire.Hosting/Resources/xlf/CommandStrings.ru.xlf | Adds Rebuild localized string entries (new/untranslated). |
| src/Aspire.Hosting/Resources/xlf/CommandStrings.pt-BR.xlf | Adds Rebuild localized string entries (new/untranslated). |
| src/Aspire.Hosting/Resources/xlf/CommandStrings.pl.xlf | Adds Rebuild localized string entries (new/untranslated). |
| src/Aspire.Hosting/Resources/xlf/CommandStrings.ko.xlf | Adds Rebuild localized string entries (new/untranslated). |
| src/Aspire.Hosting/Resources/xlf/CommandStrings.ja.xlf | Adds Rebuild localized string entries (new/untranslated). |
| src/Aspire.Hosting/Resources/xlf/CommandStrings.it.xlf | Adds Rebuild localized string entries (new/untranslated). |
| src/Aspire.Hosting/Resources/xlf/CommandStrings.fr.xlf | Adds Rebuild localized string entries (new/untranslated). |
| src/Aspire.Hosting/Resources/xlf/CommandStrings.es.xlf | Adds Rebuild localized string entries (new/untranslated). |
| src/Aspire.Hosting/Resources/xlf/CommandStrings.de.xlf | Adds Rebuild localized string entries (new/untranslated). |
| src/Aspire.Hosting/Resources/xlf/CommandStrings.cs.xlf | Adds Rebuild localized string entries (new/untranslated). |
| src/Aspire.Hosting/Resources/CommandStrings.resx | Adds RebuildName and RebuildDescription strings. |
| src/Aspire.Hosting/Resources/CommandStrings.Designer.cs | Updates the generated resource accessors for rebuild strings. |
| src/Aspire.Hosting/ProjectResourceBuilderExtensions.cs | Creates a hidden ProjectRebuilderResource for project resources in run mode. |
| src/Aspire.Hosting/DistributedApplicationBuilder.cs | Registers the new change detection hosted service in run mode. |
| src/Aspire.Hosting/Build/ProjectFileClosure.cs | Implements timestamp-based closure and change detection helper. |
| src/Aspire.Hosting/Build/ProjectChangeDetectionService.cs | Adds opt-in background monitoring of running projects and user notifications. |
| src/Aspire.Hosting/Build/ProjectBuildHelper.cs | Adds MSBuild -getItem JSON parsing + directory-scan fallback to build a closure. |
| src/Aspire.Hosting/ApplicationModel/ProjectRebuilderResource.cs | Adds the hidden executable resource type used for rebuilds. |
| src/Aspire.Hosting/ApplicationModel/KnownResourceCommands.cs | Introduces the rebuild command name constant. |
| src/Aspire.Hosting/ApplicationModel/CommandsConfigurationExtensions.cs | Wires the rebuild command into lifecycle commands and forwards build logs. |
Files not reviewed (1)
- src/Aspire.Hosting/Resources/CommandStrings.Designer.cs: Language not supported
src/Aspire.Hosting/ApplicationModel/CommandsConfigurationExtensions.cs
Outdated
Show resolved
Hide resolved
🎬 CLI E2E Test RecordingsThe following terminal recordings are available for commit
📹 Recordings uploaded automatically from CI run #23032713812 |
Implements the core rebuild command feature from issue #14970. Key changes: - Add 'rebuild' to KnownResourceCommands - Create ProjectRebuilderResource (hidden ExecutableResource that runs dotnet build) - Register rebuilder resource per ProjectResource in WithProjectDefaults (run mode only) - Uses ExplicitStartupAnnotation so it doesn't auto-start - Hidden from dashboard, excluded from manifest - Add rebuild command to CommandsConfigurationExtensions for ProjectResource - Stops main resource, starts rebuilder, funnels build logs to main resource console - Waits for build completion, restarts main resource on success - Add command string resources and localization entries - Add tests for rebuild command state transitions and registration Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds a background service that monitors source files for project resources and notifies users when a rebuild is needed. Key changes: - ProjectBuildHelper: queries MSBuild for project file closure (Compile items, ProjectReference items) and records timestamps. Falls back to directory scan if MSBuild JSON parsing fails. - ProjectFileClosure: model for the file set with GetChangedFiles() method. - ProjectChangeDetectionService: BackgroundService that watches for project resources reaching Running state, captures their file closure, and periodically checks for timestamp changes. Shows notification via IInteractionService and logs to the resource console. - Opt-in via ASPIRE_PROJECT_CHANGE_DETECTION=true env var. - Configurable interval via ASPIRE_PROJECT_CHANGE_DETECTION_INTERVAL (default 10s). - Includes debouncing (2s after last change before notifying). - Registered in DistributedApplicationBuilder (run mode only). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Unit tests for ProjectFileClosure.GetChangedFiles() covering: - No changes detected - Single file modified - File deleted (graceful handling) - Multiple files with partial changes - Empty closure - Non-existent file paths Integration tests for ProjectBuildHelper: - Non-existent project path (graceful null return) - Cancellation handling Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Aspire.Hosting.Build namespace collided with the Build type in Aspire.Hosting.Docker/Resources/ComposeNodes/Service.cs, causing CS0118: 'Build' is a namespace but is used like a type. Renamed to Aspire.Hosting.Rebuild to avoid the conflict. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
No need for a separate sub-namespace for these internal types. Removes the Rebuild/ subdirectory and places files directly in the Aspire.Hosting namespace. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Capture the ProjectResource from the closure directly instead of
looking it up by context.ResourceName, which is the instance name
(e.g. 'myproject-0') rather than the resource name ('myproject').
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The orchestrator's StartResourceAsync expects DCP instance names (from GetResolvedResourceNames), not model resource names. Using the model name caused 'resource not found' errors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The --project flag is a dotnet CLI flag, not an MSBuild flag. When DCP invokes the executable, it passes args directly to MSBuild which doesn't recognize --project. Pass the project path as a positional argument instead. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rewrite ProjectBuildHelper to follow the CLI's DotNetCliRunner pattern: - Use 'dotnet msbuild' for evaluation (not 'dotnet build') - Add -getProperty:MSBuildVersion workaround for MSBuild bug #12490 (single property query doesn't return valid JSON) - Add retry logic (3 attempts) for MSBuild server contention that can produce exit code 0 but empty output - Prefer FullPath over Identity for item paths when available - Better separation: EvaluateMSBuildAsync handles process/retry, ParseEvaluationOutput handles JSON parsing Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Three fixes: - Use configuration.GetBool() instead of GetValue<bool>() to match codebase conventions and properly parse env var string values - Check interactionService.IsAvailable before calling PromptNotificationAsync (throws if dashboard not connected) - Pass NotificationInteractionOptions with Intent=Information so the banner renders with proper styling - Wrap notification call in try/catch to prevent background task crash Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The AddsDefaultsCommandsToResources test expected 3 commands (start, stop, restart) for all resource types, but project resources now also have a rebuild command. Added HasKnownProjectCommandAnnotations helper to validate the 4-command set for project resources. Also improved notification logging in ProjectChangeDetectionService. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Change detection should always run in run mode, not require an explicit ASPIRE_PROJECT_CHANGE_DETECTION=true opt-in. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Raise key log messages from Debug to Information level so users can see change detection activity in the AppHost console output: - File closure capture start and result - Change detection events - Notification dispatch This helps diagnose whether the service is running, detecting changes, and sending notifications. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The notification banner now includes a 'Rebuild' button that triggers the rebuild command directly when clicked, instead of just telling the user to use the command manually. Uses ResourceCommandService.ExecuteCommandAsync to invoke the rebuild command on the project resource when the user clicks the button. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… test assertions - Clamp change detection interval to > 0 to prevent zero/negative delay - Use KnownResourceStates.FailedToStart instead of custom 'Build failed' string so dashboard command enablement works correctly - Replace always-true test assertions with explicit Assert.Null checks Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add feature flag: features:enableDotNetProjectChangeDetection (default true) Set via: aspire config set -g features.enableDotNetProjectChangeDetection false - Add interval setting: dotNetProjectChangeDetectionIntervalSeconds (default 5s) Set via: aspire config set -g dotNetProjectChangeDetectionIntervalSeconds 100 - Add constants to KnownConfigNames for both settings - Extract DefaultCheckIntervalSeconds constant (5 seconds) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
186179d to
4ff4e29
Compare
Remove the auto-detection of source file changes (banner notifications, dotnet msbuild evaluation, file closure tracking). Keep only the rebuild command itself for now. Removed files: - ProjectChangeDetectionService.cs - ProjectBuildHelper.cs - ProjectFileClosure.cs - ProjectBuildHelperTests.cs - ProjectFileClosureTests.cs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move the rebuild command logic out of AddLifeCycleCommands into its own AddRebuildCommand method. Reorder RebuildCommand constant to follow RestartCommand in KnownResourceCommands. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
src/Aspire.Hosting/ApplicationModel/CommandsConfigurationExtensions.cs
Outdated
Show resolved
Hide resolved
src/Aspire.Hosting/ApplicationModel/CommandsConfigurationExtensions.cs
Outdated
Show resolved
Hide resolved
src/Aspire.Hosting/ApplicationModel/CommandsConfigurationExtensions.cs
Outdated
Show resolved
Hide resolved
src/Aspire.Hosting/ApplicationModel/CommandsConfigurationExtensions.cs
Outdated
Show resolved
Hide resolved
src/Aspire.Hosting/ApplicationModel/CommandsConfigurationExtensions.cs
Outdated
Show resolved
Hide resolved
src/Aspire.Hosting/ApplicationModel/CommandsConfigurationExtensions.cs
Outdated
Show resolved
Hide resolved
- Reword RebuildDescription to be shorter - Localize 'rebuilder not found' error message via CommandStrings.resx - Add KnownResourceStates.Building constant, replace magic string - Set Building state BEFORE stopping to prevent double-rebuild race - Add 10-minute timeout for build completion (hung build protection) - Handle cancellation gracefully without logging 'Build failed' - Extract StopLogForwardingAsync helper Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Summary of additional fixes beyond the rebuild commandWhile implementing and testing the rebuild command with replicated resources ( Bug 1:
|
… rebuilds - Start, stop, and restart commands are now disabled when the resource is in Building state, preventing interference during a rebuild. - When a rebuild succeeds but no replicas were running (resource was stopped), restore each replica to its pre-build state instead of leaving it stuck in Building. - Added test cases for Building state on start, stop, and restart command state callbacks. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
I forgot to mention, projects in a waiting state should probably be rebuildable. |
Resources stuck in Waiting (e.g. dependency not healthy) should be rebuildable. Added Waiting to BuildableStates and treat Waiting replicas as active for post-rebuild restart. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Rebuilding waiting resources fails: Repo tests:
|
File-based apps (.cs files via AddCSharpApp) rebuild automatically on restart, so the explicit Rebuild command is unnecessary and confusing. The guard in AddRebuilderResource already skipped creating the hidden rebuilder resource for file-based apps, but AddRebuildCommand in CommandsConfigurationExtensions was called unconditionally for all ProjectResource types. This adds the same IsFileBasedApp check to prevent the command annotation from being added. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add outer catch for context cancellation in ExecuteRebuildAsync so the resource state is restored to Exited instead of being stuck in Building forever with all commands disabled. - Remove dead code (unreachable context.CancellationToken check). - Fix RebuilderResourceNotFound XLF entries: correct indentation from 8-space to 6-space, remove extra translate/xml:space attributes. - Restore trailing newlines on resx and all 13 XLF files. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Addressed all three feedback items in c7c0cb9: 1. Building state stuck on cancellation — Added an outer 2. XLF formatting — Fixed all 13 XLF files: corrected indentation from 8-space to 6-space for the 3. Trailing newlines — Restored trailing newlines on the .resx file and all 13 XLF files. |
src/Aspire.Hosting/ApplicationModel/CommandsConfigurationExtensions.cs
Outdated
Show resolved
Hide resolved
|
Good question. The docs ( That said, looking at the codebase, the two states are functionally identical — same dashboard rendering (stop icon, info color), same terminal-state behavior, no branching logic differentiates them. Could be worth considering consolidating them in a future PR, but that would touch containers, projects, dashboard, and docs so probably not in scope here. Happy to switch to |
Change cancellation handler to use KnownResourceStates.Finished instead of Exited per PR feedback. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Skip stopping and restarting replicas that are in Waiting state during rebuild. Waiting replicas have no DCP Executable to stop (their lifecycle is blocked at WaitForInBeforeResourceStartedEvent). The build output on disk is updated, so when dependencies become ready the new binary is launched automatically. Also avoid setting Building state on Waiting replicas, which would unblock WaitForInBeforeResourceStartedEvent and launch the old binary while the build is in progress. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When a resource is rebuilt from a stopped/terminal state, the stop command was transitioning from Hidden to Disabled, causing it to briefly appear (pulsing) in the dashboard. Since there is no process to stop during a build, the stop button should remain Hidden. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
When a short-lived resource (like the rebuilder) is deleted while its log stream is still active with follow: true, the DCP API returns 404 NotFound. Previously this was caught by the generic Exception handler and logged as an error. Now it's caught specifically and logged at Debug level, since resource deletion during log streaming is a normal lifecycle event. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Re: error output in apphost logs (basketservice-rebuilder 404 NotFound) Root cause: The rebuilder is a short-lived DCP executable. When Fix: Added a specific catch for Pushed in 7d51c9c. |


Description
Adds a rebuild command to .NET project resources and a change detection service that monitors source files and prompts users when a rebuild is needed.
Fixes #14970
Rebuild Command
ProjectResourceinstances in the Aspire dashboardProjectRebuilderResource(anExecutableResourcesubclass) that runsdotnet buildon demanddotnet build→ Restart resource[build]prefix so users can see progress and errors inlineExplicitStartupAnnotation(only starts on demand) and is hidden from the dashboardChange Detection Service
ProjectChangeDetectionService: aBackgroundServicethat monitors source files for running project resourcesdotnet msbuild -getItemJSON outputASPIRE_PROJECT_CHANGE_DETECTION_INTERVAL)IInteractionServicewhen changes are detected, with a Rebuild button that triggers the rebuild directlyFollow-up items
--no-buildoptimization after rebuild to avoid double-build on restartdotnet watchmode (potentially hide rebuild when watch is active)Checklist
aspire.devissue: