Skip to content

Fix cross-compiled CLI bundles missing DCP (win-arm64, linux-arm64, linux-musl-x64)#15529

Open
davidfowl wants to merge 2 commits intorelease/13.2from
cherry-pick/fix-aspire-update-error
Open

Fix cross-compiled CLI bundles missing DCP (win-arm64, linux-arm64, linux-musl-x64)#15529
davidfowl wants to merge 2 commits intorelease/13.2from
cherry-pick/fix-aspire-update-error

Conversation

@davidfowl
Copy link
Contributor

@davidfowl davidfowl commented Mar 24, 2026

Description

Cherry-pick of #15522 to release/13.2.

Problem

Bundle.proj's _RestoreDcpPackage target runs dotnet restore on the AppHost project to download the DCP (Developer Control Plane) NuGet package. The AppHost's PackageDownload uses $(BuildOs)-$(BuildArch) to select the platform-specific DCP package (e.g., Microsoft.DeveloperControlPlane.windows-amd64).

The problem is that BuildOs and BuildArch are defined in Directory.Build.props and auto-detect from the build machine's OS/architecture:

<BuildOs Condition="$([MSBuild]::IsOsPlatform('Linux'))">linux</BuildOs>
<BuildOs Condition="$([MSBuild]::IsOsPlatform('OSX'))">darwin</BuildOs>
<BuildOs Condition=" '$(BuildOs)' == '' ">windows</BuildOs>
<BuildArch Condition=" '$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)' == 'Arm64' ">arm64</BuildArch>
<BuildArch Condition=" '$(BuildArch)' == '' ">amd64</BuildArch>

When cross-compiling (e.g., building a win-arm64 bundle on a win-x64 CI agent), the restore downloads the build machine's DCP (windows-amd64) instead of the target's (windows-arm64). CreateLayout then silently skips the missing DCP, producing a bundle with managed/ but no dcp/. This causes "no valid layout found" failures at runtime.

Affected RIDs: win-arm64, linux-arm64, linux-musl-x64 — any RID where the target platform differs from the CI runner.

Fix

eng/Bundle.proj: Map TargetRid to BuildOs/BuildArch and pass them explicitly to the restore command so the correct DCP package is downloaded for the target platform.

tools/CreateLayout/Program.cs: Promote missing DCP from a silent warning to a hard error, preventing broken bundles from being produced.

Why a hand-rolled mapping instead of reusing Aspire.RuntimeIdentifier.Tool?

The repo already has Aspire.RuntimeIdentifier.Tool (in src/Aspire.AppHost.Sdk/) which uses NuGet's runtime graph (RuntimeGraph) to resolve the best-matching RID for a platform. The Aspire SDK used this to select the correct Dashboard and DCP packages at build time (see PR #5695).

However, it can't be directly reused here because DCP packages use Go-style naming conventions, not NuGet RIDs:

NuGet RID DCP package suffix
win-x64 windows-amd64
osx-arm64 darwin-arm64
linux-musl-x64 linux-musl-amd64

The tool resolves win-x64 from the runtime graph, but the DCP package is named Microsoft.DeveloperControlPlane.windows-amd64. So a mapping from NuGet RID conventions → DCP naming (winwindows, osxdarwin, x64amd64) is still required regardless. The mapping is ~6 lines of MSBuild conditions in Bundle.proj, which is pragmatic for a servicing fix.

Eliminating this mapping entirely would require the DCP packages to adopt NuGet RID naming, which is outside this repo's control.

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No
  • Does the change require an update in our Aspire docs?

Copilot AI and others added 2 commits March 24, 2026 07:10
…nux-musl-x64

Bundle.proj's _RestoreDcpPackage target now maps TargetRid to BuildOs/BuildArch
and passes them to the AppHost restore, ensuring the correct DCP NuGet package
is downloaded for the target platform instead of the build machine's platform.

CreateLayout now throws when DCP is not found instead of silently producing
a broken bundle that would fail layout validation at runtime.

Co-authored-by: davidfowl <95136+davidfowl@users.noreply.github.com>
Agent-Logs-Url: https://github.com/microsoft/aspire/sessions/a71f0181-f863-4d63-b275-47c8eb198dee
Copilot AI review requested due to automatic review settings March 24, 2026 14:10
@github-actions
Copy link
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 -- 15529

Or

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

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 fixes cross-compilation of Aspire CLI bundles by ensuring the correct DCP NuGet package is restored for the target RID (rather than the build machine RID), and by preventing CreateLayout from producing bundles that are missing DCP.

Changes:

  • Map TargetRid → DCP BuildOs/BuildArch in eng/Bundle.proj and pass them to the AppHost restore.
  • Fail fast in tools/CreateLayout when DCP cannot be found, instead of silently skipping it.

Reviewed changes

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

File Description
eng/Bundle.proj Adds RID→(BuildOs, BuildArch) mapping and uses it during DCP restore so cross-compiled bundles restore the correct DCP package.
tools/CreateLayout/Program.cs Throws an error when DCP is missing to avoid producing invalid layouts/bundles.

Comment on lines +122 to +124
<_BundleBuildArch Condition="$(TargetRid.EndsWith('-x64'))">amd64</_BundleBuildArch>
<_BundleBuildArch Condition="$(TargetRid.EndsWith('-arm64'))">arm64</_BundleBuildArch>
<_BundleBuildArch Condition="$(TargetRid.EndsWith('-x86'))">386</_BundleBuildArch>
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

The new RID→BuildArch mapping includes -x86386, but this repo doesn’t appear to ship a Microsoft.DeveloperControlPlane.*-386 package (Version.Details.xml lists amd64/arm64 variants only). As written, TargetRid=*-x86 would pass the mapping validation and then fail later during restore with a less actionable NuGet error. Consider removing the -x86 mapping (and updating the preceding comment) or explicitly erroring when TargetRid ends with -x86 so the failure is immediate and self-explanatory.

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Contributor

Re-running the failed jobs in the CI workflow for this pull request because 1 job was identified as retry-safe transient failures in the CI run attempt.
GitHub was asked to rerun all failed jobs for that attempt, and the rerun is being tracked in the rerun attempt.
The job links below point to the failed attempt jobs that matched the retry-safe transient failure rules.

@github-actions
Copy link
Contributor

🎬 CLI E2E Test Recordings — 51 recordings uploaded (commit d5b2bea)

View recordings
Test Recording
AddPackageInteractiveWhileAppHostRunningDetached ▶️ View Recording
AddPackageWhileAppHostRunningDetached ▶️ View Recording
AgentCommands_AllHelpOutputs_AreCorrect ▶️ View Recording
AgentInitCommand_DefaultSelection_InstallsSkillOnly ▶️ View Recording
AgentInitCommand_MigratesDeprecatedConfig ▶️ View Recording
AspireAddPackageVersionToDirectoryPackagesProps ▶️ View Recording
AspireUpdateRemovesAppHostPackageVersionFromDirectoryPackagesProps ▶️ View Recording
Banner_DisplayedOnFirstRun ▶️ View Recording
Banner_DisplayedWithExplicitFlag ▶️ View Recording
CertificatesClean_RemovesCertificates ▶️ View Recording
CertificatesTrust_WithNoCert_CreatesAndTrustsCertificate ▶️ View Recording
CertificatesTrust_WithUntrustedCert_TrustsCertificate ▶️ View Recording
ConfigSetGet_CreatesNestedJsonFormat ▶️ View Recording
CreateAndRunAspireStarterProject ▶️ View Recording
CreateAndRunAspireStarterProjectWithBundle ▶️ View Recording
CreateAndRunEmptyAppHostProject ▶️ View Recording
CreateAndRunJsReactProject ▶️ View Recording
CreateAndRunPythonReactProject ▶️ View Recording
CreateAndRunTypeScriptEmptyAppHostProject ▶️ View Recording
CreateAndRunTypeScriptStarterProject ▶️ View Recording
CreateStartAndStopAspireProject ▶️ View Recording
CreateTypeScriptAppHostWithViteApp ▶️ View Recording
DescribeCommandResolvesReplicaNames ▶️ View Recording
DescribeCommandShowsRunningResources ▶️ View Recording
DetachFormatJsonProducesValidJson ▶️ View Recording
DoctorCommand_DetectsDeprecatedAgentConfig ▶️ View Recording
DoctorCommand_WithSslCertDir_ShowsTrusted ▶️ View Recording
DoctorCommand_WithoutSslCertDir_ShowsPartiallyTrusted ▶️ View Recording
GlobalMigration_HandlesCommentsAndTrailingCommas ▶️ View Recording
GlobalMigration_HandlesMalformedLegacyJson ▶️ View Recording
GlobalMigration_PreservesAllValueTypes ▶️ View Recording
GlobalMigration_SkipsWhenNewConfigExists ▶️ View Recording
GlobalSettings_MigratedFromLegacyFormat ▶️ View Recording
InvalidAppHostPathWithComments_IsHealedOnRun ▶️ View Recording
LegacySettingsMigration_AdjustsRelativeAppHostPath ▶️ View Recording
LogsCommandShowsResourceLogs ▶️ View Recording
PsCommandListsRunningAppHost ▶️ View Recording
PsFormatJsonOutputsOnlyJsonToStdout ▶️ View Recording
PublishWithDockerComposeServiceCallbackSucceeds ▶️ View Recording
RestoreGeneratesSdkFiles ▶️ View Recording
RunFromParentDirectory_UsesExistingConfigNearAppHost ▶️ View Recording
RunWithMissingAwaitShowsHelpfulError ▶️ View Recording
SecretCrudOnDotNetAppHost ▶️ View Recording
SecretCrudOnTypeScriptAppHost ▶️ View Recording
StagingChannel_ConfigureAndVerifySettings_ThenSwitchChannels ▶️ View Recording
StopAllAppHostsFromAppHostDirectory ▶️ View Recording
StopAllAppHostsFromUnrelatedDirectory ▶️ View Recording
StopNonInteractiveMultipleAppHostsShowsError ▶️ View Recording
StopNonInteractiveSingleAppHost ▶️ View Recording
StopWithNoRunningAppHostExitsSuccessfully ▶️ View Recording
TypeScriptAppHostWithProjectReferenceIntegration ▶️ View Recording

📹 Recordings uploaded automatically from CI run #23493903454

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.

3 participants