Skip to content

Fix multi-platform project container builds (RuntimeIdentifiers passed as one RID) - #21514

Open
David Fowler (davidfowl) wants to merge 1 commit into
mainfrom
aspireagent/fix-container-rid-list-9281
Open

David Fowler (davidfowl) wants to merge 1 commit into
mainfrom
aspireagent/fix-container-rid-list-9281

Conversation

@davidfowl

Copy link
Copy Markdown
Collaborator

Description

Multi-platform container builds of .NET projects fail again in 13.6. With WithContainerBuildOptions(ctx => ctx.TargetPlatform = ContainerTargetPlatform.AllLinux), the build fails like this (report on #9281):

error NETSDK1083: The specified RuntimeIdentifier 'linux-x64;linux-arm64' is not recognized.

Root cause

#19997 moved AddTargetPlatformArguments to MsBuildResponseFileFactory.CreatePropertyArgument, which percent-escapes ; as %3B. That protects the switch from MSBuild's command-line splitting. However, an escaped ; also stays literal when MSBuild expands the property into items. So the SDK sees RuntimeIdentifiers as one RID, linux-x64;linux-arm64, and its per-RID inner builds fail with NETSDK1083. Before #19997 the list was passed quoted (/p:RuntimeIdentifiers=\"linux-x64;linux-arm64\"), which is why 13.5.x worked.

Fix

  • Add MsBuildResponseFileFactory.CreateListPropertyArgument. It percent-escapes each entry and joins the entries with real ; inside quotes (--property:RuntimeIdentifiers="linux-x64;linux-arm64"). MSBuild's switch parser therefore keeps the value as one property, and item expansion still yields one RID per entry.
  • Use it for RuntimeIdentifiers and ContainerRuntimeIdentifiers when more than one platform is targeted. Single-RID and all other properties are unchanged.

Reproduction and before/after evidence

Minimal MSBuild semantics. I used a project with <R Include="$(RuntimeIdentifiers)" />:

Switch @(R)
--property:RuntimeIdentifiers=linux-x64%3Blinux-arm64 (main) 1 item: linux-x64;linux-arm64
--property:RuntimeIdentifiers="linux-x64;linux-arm64" (this PR) 2 items: linux-x64, linux-arm64

Regression test. I strengthened ResourceContainerImageBuilderTests.BuildImageAsync_MultiPlatformArgumentsSurviveMsBuildParsing (both the project and file-based variants). It already round-tripped the emitted switches through a real dotnet msbuild, but it only checked -getProperty, which prints the unescaped string, so it could not see the bug. It now also expands both properties into items and asserts ["linux-x64", "linux-arm64"].

  • Before the fix, with the probe fed the %3B arguments main emits, both variants fail:
    Assert.Equal() Failure: Collections differ at index 0
    Expected: "linux-x64"
    Actual:   "linux-x64;linux-arm64"
    
  • After the fix, both variants pass, and so does BuildImageAsync_DynamicPropertiesSurviveMsBuildParsing.

E2E evidence (real SDK container publish)

I ran a real dotnet publish /t:PublishContainer on a fresh dotnet new web project with SDK 11.0.100-rc.2, archive destination, and the exact property switches Aspire emits:

  • Before (%3B): error NETSDK1083: The specified RuntimeIdentifier 'linux-x64;linux-arm64' is not recognized. This is the same error as in the issue report.
  • After (quoted list):
    Building image 'api' for runtime identifier 'linux-x64' on top of base image 'mcr.microsoft.com/dotnet/aspnet:11.0.0-rc.2'.
    Building image 'api' for runtime identifier 'linux-arm64' on top of base image 'mcr.microsoft.com/dotnet/aspnet:11.0.0-rc.2'.
    [CreateImageIndex] Building image index 'api:latest' on top of manifests sha256:8ea75b14…, sha256:d729860b…
    Pushed image 'api:latest' to local archive
    

Validation

  • ResourceContainerImageBuilderTests: 67 passed, 0 failed locally. I excluded the 11 tests that build real images, because this machine has too little disk for base-image layers and can't reach NuGet audit (NU1900). CI runs them.
  • src/Aspire.Hosting.Dotnet, which also compiles the shared MsBuildResponseFile.cs, builds cleanly.

Fixes #9281

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

🤖 Posted by AspireAgent (AI Agent)

Percent-escaping ';' in the RuntimeIdentifiers/ContainerRuntimeIdentifiers
switches made MSBuild treat 'linux-x64;linux-arm64' as a single RID, so
multi-platform project container builds failed with NETSDK1083. Escape each
RID and quote the list so ';' stays a real item separator.

Fixes #9281

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

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 -- 21514

Or

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

@github-actions github-actions Bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Oct 31, 2026
@aspire-repo-bot
aspire-repo-bot Bot requested a balanced review from Copilot October 31, 2026 02:37
@github-actions

Copy link
Copy Markdown
Contributor

Tests selector

52 / 100 PR test projects · 4 PR jobs, from 3 changed files.

Selected PR test projects (52 / 100)

Aspire.Cli.EndToEnd.Tests, Aspire.Hosting.Analyzers.Tests, Aspire.Hosting.Azure.EntraId.Tests, Aspire.Hosting.Azure.Kubernetes.Tests, Aspire.Hosting.Azure.Kusto.Tests, Aspire.Hosting.Azure.Provisioning.Tests, Aspire.Hosting.Azure.Tests, Aspire.Hosting.Blazor.Tests, Aspire.Hosting.Browsers.Tests, Aspire.Hosting.CodeGeneration.Go.Tests, Aspire.Hosting.CodeGeneration.Java.Tests, Aspire.Hosting.CodeGeneration.Python.Tests, Aspire.Hosting.CodeGeneration.Rust.Tests, Aspire.Hosting.CodeGeneration.TypeScript.Tests, Aspire.Hosting.Containers.Tests, Aspire.Hosting.DevTunnels.Tests, Aspire.Hosting.Docker.Tests, Aspire.Hosting.Dotnet.Tests, Aspire.Hosting.DotnetTool.Tests, Aspire.Hosting.EntityFrameworkCore.Tests, Aspire.Hosting.Foundry.Tests, Aspire.Hosting.Garnet.Tests, Aspire.Hosting.Go.Tests, Aspire.Hosting.Java.Tests, Aspire.Hosting.JavaScript.Tests, Aspire.Hosting.Kafka.Tests, Aspire.Hosting.Keycloak.Tests, Aspire.Hosting.Kubernetes.Tests, Aspire.Hosting.Maui.Tests, Aspire.Hosting.Milvus.Tests, Aspire.Hosting.MongoDB.Tests, Aspire.Hosting.MySql.Tests, Aspire.Hosting.Nats.Tests, Aspire.Hosting.OpenAI.Tests, Aspire.Hosting.Oracle.Tests, Aspire.Hosting.Orleans.Tests, Aspire.Hosting.PostgreSQL.Tests, Aspire.Hosting.Python.Tests, Aspire.Hosting.Qdrant.Tests, Aspire.Hosting.RabbitMQ.Tests, Aspire.Hosting.Radius.Tests, Aspire.Hosting.Redis.Tests, Aspire.Hosting.RemoteHost.Tests, Aspire.Hosting.Rust.Tests, Aspire.Hosting.Seq.Tests, Aspire.Hosting.SqlServer.Tests, Aspire.Hosting.Testing.Tests, Aspire.Hosting.Tests, Aspire.Hosting.Valkey.Tests, Aspire.Hosting.Yarp.Tests, Aspire.Playground.Tests, Aspire.Templates.Tests

Selected PR jobs (4)

cli-starter-validation, extension-e2e, polyglot, typescript-api-compat


How these were chosen — grouped by what changed

⚠️ 37 of the 52 selected test projects come from a single change — src/Aspire.Hosting/Publishing/ResourceContainerImageManager.cs.

🔧 src/Aspire.Hosting/Publishing/ResourceContainerImageManager.cs (changed source)
→ 37 via the project graph

show 37

Aspire.Hosting.Analyzers.Tests (2 hops), Aspire.Hosting.Azure.Kubernetes.Tests (2 hops), Aspire.Hosting.Azure.Kusto.Tests (2 hops), Aspire.Hosting.Azure.Provisioning.Tests (3 hops), Aspire.Hosting.Azure.Tests, Aspire.Hosting.Browsers.Tests (2 hops), Aspire.Hosting.CodeGeneration.Go.Tests, Aspire.Hosting.CodeGeneration.Java.Tests, Aspire.Hosting.CodeGeneration.Python.Tests, Aspire.Hosting.CodeGeneration.Rust.Tests, Aspire.Hosting.CodeGeneration.TypeScript.Tests, Aspire.Hosting.Containers.Tests (2 hops), Aspire.Hosting.DotnetTool.Tests (2 hops), Aspire.Hosting.Garnet.Tests (2 hops), Aspire.Hosting.Go.Tests (2 hops), Aspire.Hosting.Java.Tests (2 hops), Aspire.Hosting.JavaScript.Tests (2 hops), Aspire.Hosting.Kafka.Tests (2 hops), Aspire.Hosting.Keycloak.Tests (2 hops), Aspire.Hosting.Maui.Tests, Aspire.Hosting.MongoDB.Tests (2 hops), Aspire.Hosting.MySql.Tests (2 hops), Aspire.Hosting.Nats.Tests (2 hops), Aspire.Hosting.OpenAI.Tests (2 hops), Aspire.Hosting.Oracle.Tests (2 hops), Aspire.Hosting.Orleans.Tests (2 hops), Aspire.Hosting.PostgreSQL.Tests (2 hops), Aspire.Hosting.Python.Tests (2 hops), Aspire.Hosting.RabbitMQ.Tests (2 hops), Aspire.Hosting.Redis.Tests (2 hops), Aspire.Hosting.RemoteHost.Tests, Aspire.Hosting.Rust.Tests (2 hops), Aspire.Hosting.Seq.Tests (2 hops), Aspire.Hosting.SqlServer.Tests (2 hops), Aspire.Hosting.Valkey.Tests (2 hops), Aspire.Hosting.Yarp.Tests (2 hops), Aspire.Playground.Tests

🔧 src/Shared/MsBuildResponseFile.cs (changed source)
→ 11 via the project graph: Aspire.Hosting.Azure.EntraId.Tests, Aspire.Hosting.DevTunnels.Tests, Aspire.Hosting.Docker.Tests, Aspire.Hosting.Dotnet.Tests, Aspire.Hosting.EntityFrameworkCore.Tests, Aspire.Hosting.Foundry.Tests, Aspire.Hosting.Kubernetes.Tests, Aspire.Hosting.Milvus.Tests, Aspire.Hosting.Qdrant.Tests, Aspire.Hosting.Radius.Tests, Aspire.Hosting.Testing.Tests

📦 affected project Aspire.Hosting.Dotnet
→ 2 tests: Aspire.Cli.EndToEnd.Tests, Aspire.Templates.Tests

🧪 tests/Aspire.Hosting.Tests/Publishing/ResourceContainerImageManagerTests.cs (changed test)
→ 1 directly: Aspire.Hosting.Tests
→ 1 via the project graph: Aspire.Hosting.Blazor.Tests

📦 affected project Aspire.Hosting
→ 1 test: Aspire.Cli.EndToEnd.Tests

Job reasons

Job Triggered by
cli-starter-validation affected project Aspire.Hosting.Dotnet
extension-e2e • src/Aspire.Hosting/Publishing/ResourceContainerImageManager.cs
• affected project Aspire.Hosting
polyglot • affected project Aspire.Hosting
• affected project Aspire.Hosting.Azure.Provisioning.KeyVault
typescript-api-compat affected project Aspire.Hosting

Selection computed for commit 2e08ef3.

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.

🟢 Approval recommended

No active review issues are recorded for the change addressing multi-platform runtime identifier handling.

No open issues 🎉

What changed in this PR

Fixes a regression in Aspire’s multi-platform .NET container publishing where MSBuild treated multiple runtime identifiers as one value, causing NETSDK1083.

Changes:

  • Adds a list-property argument helper that escapes individual entries while preserving quoted semicolon separators.
  • Uses the helper for multi-platform runtime properties, leaving single-platform arguments unchanged.
  • Strengthens regression tests to verify that MSBuild expands both runtime properties into separate items.
File Description
tests/​Aspire.Hosting.Tests/​Publishing/​ResourceContainerImageManagerTests.cs Verifies quoted arguments and separate runtime items for project and file-based builds.
src/​Shared/​MsBuildResponseFile.cs Adds argument formatting for list-valued MSBuild properties.
src/​Aspire.Hosting/​Publishing/​ResourceContainerImageManager.cs Applies list formatting to multi-platform runtime properties.

Review effort: Balanced

Copy link
Copy Markdown
Collaborator Author

CI note for exact head 2e08ef3f, run 41160826284. Attempt 1 had 331 passing checks and 5 failures. None of the failures is in code this PR touches. All 5 are known infrastructure problems on Windows, so I reran the failed jobs once:

Job Attempt 1
Hosting.Java (windows-latest) The hosted runner lost communication with the server (log BlobNotFound)
VS Code extension E2E (Windows, command-palette) The hosted runner lost communication with the server
VS Code extension E2E (Windows, dynamic-debug-configuration) The hosted runner lost communication with the server
VS Code extension E2E (Windows, coordinated-build) The hosted runner lost communication with the server
Aspire CLI Starter Validation (Windows x64) Exceeded the 45m maximum execution time in "Create starter app and validate startup" (known hung Windows CLI-starter job)

🤖 Posted by AspireAgent (AI Agent)

This branch has not been deployed

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

Labels

needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Publishing to docker compose fails for a project with multiple RuntimeIdentifiers

2 participants