microsoft / microsoft/aspire

Provide a supported way to skip Aspire CLI bundle resolution for build-only scenarios (CI)

Open
#19,937 0 comments 0 reactions 0 assignees View on GitHub
needs-area-label
Dominant language
C#
Stars
6.3k
Forks
991
Avg merge
2d 15h
Merged PRs (30d)
196

Description

### Is there an existing issue for this?

- [x] I have searched the existing issues

### Is your feature request related to a problem? Please describe the problem.

With `AspireUseCliBundle=true`, every `dotnet build` of an AppHost on a machine with no `aspire` on `PATH` acquires the CLI through `dnx` and unpacks a bundle into `~/.aspire`. That is exactly right on a developer machine, but it is pure overhead in CI, which compiles and tests an AppHost without ever launching orchestration — DCP and the dashboard are never invoked.

Measured on GitHub Actions (`ubuntu-24.04`, .NET SDK `11.0.100-preview.6`, Aspire `13.5.3`), enabling the bundle cost ~10s on every run:

| run | Restore | Build |
| --- | --- | --- |
| `AspireUseCliBundle` unset | 26s | 154s |
| `AspireUseCliBundle` unset | 24s | 155s |
| `AspireUseCliBundle=true` | 24s | **164s** |

Worth noting the acquisition happens inside `dotnet build`, not `dotnet restore` (`ResolveAspireCliBundlePaths` runs via `DependsOnTargets` of the `Set*DiscoveryAttributes` targets, which are `BeforeTargets="GetAssemblyAttributes"`). Two consequences beyond the time:

- **The build step gains a hard network dependency**, even under `dotnet build --no-restore`, which is otherwise hermetic. `aspire.cli` is not in `packages.lock.json`, so neither locked-mode restore nor NuGet package caching covers it — this is the build-only face of #19717.
- **It repeats on every run**, since there is nothing keyed to cache it by.

As far as I can tell there is no supported way to turn this off. The complete set of non-underscore properties across `Aspire.AppHost.Sdk` and `Aspire.Hosting.AppHost` 13.5.3 is:

> `AspireCliBundlePath`, `AspireCliInvocationMode`, `AspireCliPath`, `AspireDashboardDir`, `AspireDashboardPath`, `AspireGeneratedClassesVisibility`, `AspireHostingSDKVersion`, `AspireManifestPublishOutputPath`, `AspirePublisher`, `AspireRidToolDirectory`, `AspireRidToolExecutable`, `AspireRidToolRoot`, `AspireTerminalHostDir`, `AspireTerminalHostInvocationArgs`, `AspireTerminalHostPath`, `AspireUseCliBundle`, `DcpCliPath`, `DcpDir`, `DcpExtensionsDir`, `IsAspireHost`, `SkipAddAspireDefaultReferences`, `SkipValidateAspireHostProjectResources`, `WriteAspireProjectMetadataSourcesDependsOn`

The only two `Skip*` switches are `SkipAddAspireDefaultReferences` and `SkipValidateAspireHostProjectResources`; neither affects bundle resolution.

Setting `AspireUseCliBundle=false` just for CI is not an equivalent escape hatch: it re-adds the `Aspire.Hosting.Orchestration.*` / `Aspire.Dashboard.Sdk.*` package references (so the lock file would have to differ between CI and developer machines) and it re-triggers ASPIRE010, which fails any build using `-warnaserror`.

### Describe the solution you'd like

A first-class opt-out for build-only consumers, e.g.

```xml

true

```

or, equivalently, an `AspireCliInvocationMode=None`.

Semantics: skip `ResolveAspireCliBundlePaths` and the `Set*DiscoveryAttributes` targets, emit neither ASPIRE009 nor ASPIRE010, and let the AppHost compile normally. Anything that then tried to launch orchestration from that output would fail at run time — which is the right trade for a configuration that explicitly declares "I am only building this".

This would also give #19717 a clean answer for the build-only case: an offline machine that only needs to compile could opt out rather than fail.

### Additional context

The workaround we landed on is to pre-supply the paths that the resolution exists to discover, so the target's condition is already false before it runs:

```yml
env:
DcpDir: /nonexistent/aspire-orchestration-unused-in-ci
AspireDashboardDir: /nonexistent/aspire-orchestration-unused-in-ci
```

which works against this condition in `Aspire.Hosting.AppHost.targets`:

```xml

```

Verified in a cold environment (no `aspire` on `PATH`, empty `~/.aspire`, empty NuGet cache): no `dnx` invocation, nothing downloaded, `~/.aspire` never created, and the AppHost still compiles with `-warnaserror`.

It works, but it leans on those properties not being validated, and it bakes non-existent `dcpclipath` / `aspiredashboardpath` values into the assembly as `AssemblyMetadata` — which seems like the sort of thing that could reasonably start being validated in a future release, at which point the workaround breaks. Hence the request for something supported.

Related: #19717 (DNX acquisition offline), #19715 (acquisition failures hard to troubleshoot in CI), #19714 (invalid `AspireCliInvocationMode` values silently ignored).

Contributor guide

Open the contributing guide

Research direction

Start with Aspire.Hosting.AppHost.targets, especially ResolveAspireCliBundlePaths, _ResolveAspireCliInvocation, and the Set*DiscoveryAttributes targets described in the issue. Trace their conditions and property handling, then define a supported build-only opt-out that skips resolution and emits neither ASPIRE009 nor ASPIRE010 while allowing normal compilation. Verify the behavior in a cold CI-like environment with no CLI or cached bundle.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
build-system, ci-cd, cli
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.