Skip to content

Avoid running MSBuild during aspire ls AppHost discovery in the VS Code extension#18348

Draft
ellahathaway wants to merge 10 commits into
mainfrom
ellahathaway/investigate-msbuild-storm
Draft

Avoid running MSBuild during aspire ls AppHost discovery in the VS Code extension#18348
ellahathaway wants to merge 10 commits into
mainfrom
ellahathaway/investigate-msbuild-storm

Conversation

@ellahathaway

Copy link
Copy Markdown
Contributor

Fixes #18175.

Problem

When the VS Code extension discovers AppHosts in a workspace, it calls aspire ls --format json. To classify each candidate, the CLI ran a design-time MSBuild evaluation of every matching project. In large workspaces (or on cold start) this produced an "MSBuild storm" — many concurrent evaluations that made discovery slow and CPU-heavy, even though the extension only needs a fast, best-effort list to populate the tree.

Solution

Add a --no-evaluate mode to aspire ls and have the extension opt into it. In this mode discovery is cache-first and never triggers a fresh evaluation: it uses already-known AppHost details when present, and otherwise reports a best-effort guess from project naming/layout cues. The extension treats those unverified-but-likely candidates as launchable.

CLI (src/Aspire.Cli)

  • New aspire ls --no-evaluate option. Threaded through ProjectLocator, AppHostInfoResolver, and IAppHostProject.ValidateAppHostAsync so a cache miss returns a heuristic signal instead of invoking MSBuild.
  • New candidate status possibly-buildable (alongside buildable and possibly-unbuildable) for AppHosts that look launchable but were not evaluated. Documented in docs/specs/cli-output-formats.md.
  • New capability ls-no-evaluate.v1 advertised through aspire config info so tooling can detect support before passing the flag.
  • Localized option description + new error/shared-command strings (with .xlf updates).

VS Code extension (extension/)

  • Discovery detects ls-no-evaluate.v1 via the shared ConfigInfoProvider (no extra config info call) and appends --no-evaluate only when supported. Backwards compatible: an older CLI degrades to plain aspire ls, and then to the legacy aspire extension get-apphosts fallback.
  • New appHostStatus.ts centralizes the CLI status vocabulary — buildable rule (buildable + possibly-buildable are launchable), user-facing label, and tree icon — so they can't drift apart. New neutral types/appHostCandidate.ts keeps status a string so a newer CLI value can't break deserialization.
  • Tree view renders the candidate status (icon + Status: … row).
  • Running AppHosts are no longer shown as idle "candidates."
  • On a failed run/build the stale candidate is refreshed out of the list, and the user is warned only after an actual build attempt fails (not when discovery merely reclassifies).

Testing

  • CLI unit tests: LsCommandTests, AppHostInfoResolverTests, DotNetAppHostProjectTests, ProjectLocatorTests, AppHostInfoDiskCacheTests cover --no-evaluate cache-first behavior and the new status.
  • Extension unit tests: appHostDiscovery.test.ts verifies --no-evaluate is appended when the capability is advertised and omitted (falling back to plain aspire ls) when it isn't; appHostTreeView.test.ts / appHostDataRepository.test.ts cover status rendering and candidate state.
  • Extension E2E (appHostTree.e2e.test.ts): promotion of a possibly-buildable idle candidate to buildable, warn-and-remove when a build actually fails, and reclassification on debug-session-end.
  • tsc + eslint clean; the new --no-evaluate discovery tests pass.

Video demo

no-evaluate.mp4

Copilot AI review requested due to automatic review settings June 19, 2026 23:09
@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

🚀 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/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 18348

Or

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

@github-actions

This comment has been minimized.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 adds a --no-evaluate mode to aspire ls AppHost discovery so the VS Code extension can populate its tree quickly without triggering a design-time MSBuild "storm" (one evaluation per candidate project). In this mode the CLI is cache-first: it returns already-known AppHost details when present, and otherwise emits a best-effort heuristic signal (possibly-buildable) instead of evaluating. The extension detects the new ls-no-evaluate.v1 capability via the shared ConfigInfoProvider, opts into the flag when supported, and gracefully degrades on older CLIs. It fixes #18175.

Changes:

  • CLI: thread a noEvaluate flag through ProjectLocator, AppHostInfoResolver, and IAppHostProject.ValidateAppHostAsync; add the PossiblyBuildable candidate status, the ls-no-evaluate.v1 capability, and a content-keyed disk cache path normalization; make AppHostProjectInfo.IsAspireHost nullable to represent "not evaluated."
  • Extension: centralize CLI status vocabulary in appHostStatus.ts, add a neutral appHostCandidate.ts type, render candidate status (icon + Status: row), and refine candidate refresh/warn-on-build-failure behavior.
  • Docs, localization (resx/Designer/xlf), and unit/E2E test coverage for the new mode and status.

Reviewed changes

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

Show a summary per file
File Description
src/Aspire.Cli/Caching/AppHostInfoDiskCache.cs New NormalizePathForHash for case-insensitive cache keys (macOS gap — see comment).
src/Aspire.Cli/Projects/AppHostInfoResolver.cs Cache-first heuristic fallback (CreateHeuristicInfo) and nullable IsAspireHost.
src/Aspire.Cli/Projects/DotNetAppHostProject.cs ValidateAppHostAsync(noEvaluate) returning IsNotEvaluated on null (uses == null — see comment).
src/Aspire.Cli/Projects/ProjectLocator.cs Threads noEvaluate; maps IsNotEvaluatedPossiblyBuildable; new enum value + warning.
src/Aspire.Cli/Commands/LsCommand.cs --no-evaluate option, status serialization/markup.
extension/src/utils/appHostStatus.ts (new), types/appHostCandidate.ts (new) Status vocabulary + neutral candidate type.
extension/src/utils/appHostDiscovery.ts, views/AspireAppHostTreeProvider.ts, views/AppHostDataRepository.ts, services/AppHostLaunchService.ts Capability opt-in, status rendering, candidate refresh/warn behavior.
docs/specs/cli-output-formats.md Documents --no-evaluate and possibly-buildable.
Localization (*.resx, *.Designer.cs, xlf/*) New option/error strings + generated translations.
tests/Aspire.Cli.Tests/**, extension/src/test*/** Unit/E2E coverage for cache-first behavior and new status.

Notable findings:

  • Critical bug: NormalizePathForHash (new in this PR — main appended the raw FullName) only upper-cases the Windows drive letter, yet its comment claims macOS is also case-insensitive. On macOS it returns the path unchanged, so the new ComputeKey_IsCaseInsensitiveForProjectPathOnCaseInsensitiveFilesystems test (which runs on macOS) fails, and the cache-hit feature is defeated on macOS for differently-cased paths.
  • Nit: DotNetAppHostProject.cs line 211 uses == null where the file (and AGENTS.md) consistently use is null.
Files not reviewed (2)
  • src/Aspire.Cli/Resources/ErrorStrings.Designer.cs: Generated file
  • src/Aspire.Cli/Resources/SharedCommandStrings.Designer.cs: Generated file

Comment thread src/Aspire.Cli/Caching/AppHostInfoDiskCache.cs
Comment thread src/Aspire.Cli/Projects/DotNetAppHostProject.cs Outdated
@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

ellahathaway and others added 2 commits June 19, 2026 16:53
The test case-folded the entire project path and ran on macOS, but NormalizePathForHash only normalizes the Windows drive letter, so the test failed on Windows. Scope the test to Windows, flip only the drive letter, and trim the NormalizePathForHash comment to match the actual behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

Copilot AI review requested due to automatic review settings June 19, 2026 23:58
@github-actions

This comment has been minimized.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 72 out of 74 changed files in this pull request and generated 3 comments.

Files not reviewed (2)
  • src/Aspire.Cli/Resources/ErrorStrings.Designer.cs: Generated file
  • src/Aspire.Cli/Resources/SharedCommandStrings.Designer.cs: Generated file

Comment thread src/Aspire.Cli/Projects/ProjectLocator.cs Outdated
Comment thread docs/specs/cli-output-formats.md Outdated
Comment thread src/Aspire.Cli/Projects/AppHostInfoResolver.cs Outdated
@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

ellahathaway and others added 3 commits June 19, 2026 17:52
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

Replace brittle spawn call-order assumptions with command-aware stubbing in appHostDataRepository tests, add async waits for context transitions, and add missing repository refresh stubs in appHostTreeView tests so launch-error assertions validate intended failures.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 20, 2026 00:57
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 73 out of 75 changed files in this pull request and generated 1 comment.

Files not reviewed (2)
  • src/Aspire.Cli/Resources/ErrorStrings.Designer.cs: Generated file
  • src/Aspire.Cli/Resources/SharedCommandStrings.Designer.cs: Generated file

Comment thread src/Aspire.Cli/Projects/ProjectLocator.cs Outdated
@github-actions

This comment has been minimized.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 20, 2026 01:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 73 out of 75 changed files in this pull request and generated 1 comment.

Files not reviewed (2)
  • src/Aspire.Cli/Resources/ErrorStrings.Designer.cs: Generated file
  • src/Aspire.Cli/Resources/SharedCommandStrings.Designer.cs: Generated file


/**
* AppHost candidate statuses emitted by `aspire ls`. The CLI owns this vocabulary
* (see `AppHostProjectCandidateStatus` in src/Aspire.Cli/Commands/LsCommand.cs), so the wire
@github-actions

Copy link
Copy Markdown
Contributor

Tests selector (audit mode)

The full test matrix and all jobs still run in audit mode. The tests and jobs below are what selective CI would run under enforcement.

Test projects (2 / 97)

  • Aspire.Cli.EndToEnd.Tests — affected project Aspire.Cli, via graph from src/Aspire.Cli/Resources/SharedCommandStrings.Designer.cs
  • Aspire.Cli.Teststests/Aspire.Cli.Tests/Caching/AppHostInfoDiskCacheTests.cs, tests/Aspire.Cli.Tests/Commands/ExtensionInternalCommandTests.cs, tests/Aspire.Cli.Tests/Commands/LsCommandTests.cs, tests/Aspire.Cli.Tests/Commands/RunCommandTests.cs, tests/Aspire.Cli.Tests/Commands/SecretCommandTests.cs, tests/Aspire.Cli.Tests/Projects/AppHostInfoResolverTests.cs, tests/Aspire.Cli.Tests/Projects/DotNetAppHostProjectTests.cs, tests/Aspire.Cli.Tests/Projects/ProjectLocatorTests.cs, tests/Aspire.Cli.Tests/TestServices/NoProjectFileProjectLocator.cs, tests/Aspire.Cli.Tests/TestServices/TestAppHostProjectFactory.cs, tests/Aspire.Cli.Tests/TestServices/TestProjectLocator.cs, tests/Aspire.Cli.Tests/TestServices/TestTypeScriptStarterProjectFactory.cs, via graph from tests/Aspire.Cli.Tests/Caching/AppHostInfoDiskCacheTests.cs

Jobs (6)

  • cli-starter — affected project Aspire.Cli, via test Aspire.Cli.Tests
  • deployment-e2e — affected project Aspire.Cli
  • extension-e2eextension/scripts/run-e2e.js, extension/src/extension.ts, extension/src/loc/strings.ts, extension/src/services/AppHostLaunchService.ts, extension/src/test-e2e/appHostTree.e2e.test.ts, extension/src/test-e2e/commandPalette.e2e.test.ts, extension/src/test-e2e/discoveryConfiguration.e2e.test.ts, extension/src/test-e2e/helpers/assertions.ts, extension/src/test-e2e/helpers/fixtures.ts, extension/src/test-e2e/zeroToRunning.e2e.test.ts, extension/src/test/appHostDataRepository.test.ts, extension/src/test/appHostDiscovery.test.ts, extension/src/test/appHostTreeView.test.ts, extension/src/test/extensionApi.test.ts, extension/src/types/appHostCandidate.ts, extension/src/types/configInfo.ts, extension/src/types/extensionApi.ts, extension/src/utils/appHostDiscovery.ts, extension/src/utils/appHostStatus.ts, extension/src/utils/workspace.ts, extension/src/views/AppHostDataRepository.ts, extension/src/views/AspireAppHostTreeProvider.ts, src/Aspire.Cli/Caching/AppHostInfoDiskCache.cs, src/Aspire.Cli/Commands/LsCommand.cs, src/Aspire.Cli/Projects/AppHostInfoResolver.cs, src/Aspire.Cli/Projects/AppHostProjectUtils.cs, src/Aspire.Cli/Projects/DotNetAppHostProject.cs, src/Aspire.Cli/Projects/GuestAppHostProject.cs, src/Aspire.Cli/Projects/IAppHostProject.cs, src/Aspire.Cli/Projects/ProjectLocator.cs, src/Aspire.Cli/Resources/ErrorStrings.Designer.cs, src/Aspire.Cli/Resources/ErrorStrings.resx, src/Aspire.Cli/Resources/SharedCommandStrings.Designer.cs, src/Aspire.Cli/Resources/SharedCommandStrings.resx, src/Aspire.Cli/Resources/xlf/ErrorStrings.cs.xlf, src/Aspire.Cli/Resources/xlf/ErrorStrings.de.xlf, src/Aspire.Cli/Resources/xlf/ErrorStrings.es.xlf, src/Aspire.Cli/Resources/xlf/ErrorStrings.fr.xlf, src/Aspire.Cli/Resources/xlf/ErrorStrings.it.xlf, src/Aspire.Cli/Resources/xlf/ErrorStrings.ja.xlf, src/Aspire.Cli/Resources/xlf/ErrorStrings.ko.xlf, src/Aspire.Cli/Resources/xlf/ErrorStrings.pl.xlf, src/Aspire.Cli/Resources/xlf/ErrorStrings.pt-BR.xlf, src/Aspire.Cli/Resources/xlf/ErrorStrings.ru.xlf, src/Aspire.Cli/Resources/xlf/ErrorStrings.tr.xlf, src/Aspire.Cli/Resources/xlf/ErrorStrings.zh-Hans.xlf, src/Aspire.Cli/Resources/xlf/ErrorStrings.zh-Hant.xlf, src/Aspire.Cli/Resources/xlf/SharedCommandStrings.cs.xlf, src/Aspire.Cli/Resources/xlf/SharedCommandStrings.de.xlf, src/Aspire.Cli/Resources/xlf/SharedCommandStrings.es.xlf, src/Aspire.Cli/Resources/xlf/SharedCommandStrings.fr.xlf, src/Aspire.Cli/Resources/xlf/SharedCommandStrings.it.xlf, src/Aspire.Cli/Resources/xlf/SharedCommandStrings.ja.xlf, src/Aspire.Cli/Resources/xlf/SharedCommandStrings.ko.xlf, src/Aspire.Cli/Resources/xlf/SharedCommandStrings.pl.xlf, src/Aspire.Cli/Resources/xlf/SharedCommandStrings.pt-BR.xlf, src/Aspire.Cli/Resources/xlf/SharedCommandStrings.ru.xlf, src/Aspire.Cli/Resources/xlf/SharedCommandStrings.tr.xlf, src/Aspire.Cli/Resources/xlf/SharedCommandStrings.zh-Hans.xlf, src/Aspire.Cli/Resources/xlf/SharedCommandStrings.zh-Hant.xlf, src/Aspire.Cli/Utils/EnvironmentChecker/AspireVersionCheck.cs, src/Aspire.Cli/Utils/ExtensionHelper.cs, tests/Aspire.Cli.Tests/Caching/AppHostInfoDiskCacheTests.cs, tests/Aspire.Cli.Tests/Commands/ExtensionInternalCommandTests.cs, tests/Aspire.Cli.Tests/Commands/LsCommandTests.cs, tests/Aspire.Cli.Tests/Commands/RunCommandTests.cs, tests/Aspire.Cli.Tests/Commands/SecretCommandTests.cs, tests/Aspire.Cli.Tests/Projects/AppHostInfoResolverTests.cs, tests/Aspire.Cli.Tests/Projects/DotNetAppHostProjectTests.cs, tests/Aspire.Cli.Tests/Projects/ProjectLocatorTests.cs, tests/Aspire.Cli.Tests/TestServices/NoProjectFileProjectLocator.cs, tests/Aspire.Cli.Tests/TestServices/TestAppHostProjectFactory.cs, tests/Aspire.Cli.Tests/TestServices/TestProjectLocator.cs, tests/Aspire.Cli.Tests/TestServices/TestTypeScriptStarterProjectFactory.cs, affected project Aspire.Cli
  • extension-unitextension/scripts/run-e2e.js, extension/src/extension.ts, extension/src/loc/strings.ts, extension/src/services/AppHostLaunchService.ts, extension/src/test-e2e/appHostTree.e2e.test.ts, extension/src/test-e2e/commandPalette.e2e.test.ts, extension/src/test-e2e/discoveryConfiguration.e2e.test.ts, extension/src/test-e2e/helpers/assertions.ts, extension/src/test-e2e/helpers/fixtures.ts, extension/src/test-e2e/zeroToRunning.e2e.test.ts, extension/src/test/appHostDataRepository.test.ts, extension/src/test/appHostDiscovery.test.ts, extension/src/test/appHostTreeView.test.ts, extension/src/test/extensionApi.test.ts, extension/src/types/appHostCandidate.ts, extension/src/types/configInfo.ts, extension/src/types/extensionApi.ts, extension/src/utils/appHostDiscovery.ts, extension/src/utils/appHostStatus.ts, extension/src/utils/workspace.ts, extension/src/views/AppHostDataRepository.ts, extension/src/views/AspireAppHostTreeProvider.ts
  • polyglot — affected project Aspire.Cli
  • typescript-api-compat — affected project Aspire.Cli

Selection computed for commit 37166a3.

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Avoid executing msbuild when running aspire ls via extension for apphost discovery

2 participants