Skip to content

[release/13.2] Fix SqlClient runtime asset layout on Unix#15709

Merged
joperezr merged 16 commits intorelease/13.2from
sebros/fix-sqlserver-macos
Apr 1, 2026
Merged

[release/13.2] Fix SqlClient runtime asset layout on Unix#15709
joperezr merged 16 commits intorelease/13.2from
sebros/fix-sqlserver-macos

Conversation

@sebastienros
Copy link
Copy Markdown
Contributor

@sebastienros sebastienros commented Mar 30, 2026

Description

Fixes bundled/prebuilt AppHost package layout so runtime-specific assets are preferred when available. This fixes Microsoft.Data.SqlClient loading in the SQL Server scenario on macOS/Linux, where the previous flat layout could select the non-runtime-specific assembly and fail with PlatformNotSupportedException.

This PR also includes the review follow-ups needed to make the fix more correct and maintainable:

  • pass the actual runtime identifier into the bundled restore/layout path
  • use the SDK runtime graph for RID fallback matching instead of a local heuristic
  • preserve structured runtime/resource assets during layout
  • add regression coverage for RID-specific target selection and richer RID fallback cases
  • clean up the temp-directory test pattern in the new layout tests

Fixes #15670

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?

Ensure bundled/prebuilt app hosts prefer runtime-specific assets when laying out NuGet packages so Microsoft.Data.SqlClient loads correctly on macOS and Linux. Add a regression test covering the runtime-target selection behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 30, 2026 17:06
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 30, 2026

🚀 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 -- 15709

Or

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

Ensure LayoutCommand chooses the matching native runtime target for the output root even when a generic native asset with the same file name is also present. Extend the regression test to cover the override behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
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

Fixes the prebuilt/bundled AppHost NuGet layout so runtime-specific assets are preferred when available (avoiding loading the wrong Microsoft.Data.SqlClient implementation on macOS/Linux), while also preserving structured runtimes/ and resource assets.

Changes:

  • Add a --runtime-identifier/--rid option to the LayoutCommand and use it to prefer matching runtimeTargets when copying assemblies/native assets.
  • Preserve runtimes/ subtree (and resources) in the layout output in addition to producing the probing-friendly flat root layout.
  • Plumb the current runtime identifier through the CLI restore/layout pipeline and add a regression test for runtime target selection + structured asset preservation.

Reviewed changes

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

Show a summary per file
File Description
tests/Aspire.Hosting.RemoteHost.Tests/LayoutCommandTests.cs Adds regression coverage for runtime-target preference and preserving runtimes/ + resource assets.
tests/Aspire.Hosting.RemoteHost.Tests/Aspire.Hosting.RemoteHost.Tests.csproj Adds reference needed to call LayoutCommand from tests.
src/Aspire.Managed/NuGet/Commands/LayoutCommand.cs Implements RID-aware target selection and copies runtime/resource/native assets preserving structure.
src/Aspire.Cli/Projects/PrebuiltAppHostServer.cs Passes current RID into bundled NuGet restore/layout to pick correct runtime assets.
src/Aspire.Cli/NuGet/BundleNuGetService.cs Threads RID into layout invocation and cache key to avoid cross-RID collisions.

@davidfowl
Copy link
Copy Markdown
Contributor

Do we need to embed the RID graph here?

sebastienros and others added 2 commits March 30, 2026 10:15
Add regression coverage for project.assets.json files that contain both a base target and a runtime-specific target so LayoutCommand's RID-based target selection is validated directly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Derive portable RID aliases from the requested runtime identifier rather than the current process OS, fix Windows JSON escaping in the layout command tests, and add coverage for explicit RID fallback selection.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@sebastienros
Copy link
Copy Markdown
Contributor Author

@davidfowl I believe the current resolution is simple enough to handle the cases we care about right now. Should we instead import the logic from other dependencies to be complete? Sub-question: already or later, 13.2.x or 13.3?

An option could be to add this simple fallback logic to 13.2.x and the full one in 13.3

@github-actions
Copy link
Copy Markdown
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.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
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.

  • Tests / Hosting.Redis / Hosting.Redis (windows-latest) - Post-test cleanup steps 'Upload logs, and test results | Copy CLI E2E recordings for upload | Upload CLI E2E recordings | Generate test results summary | Post Checkout code' matched the Windows process initialization failure override allowlist.

Copy link
Copy Markdown
Member

@JamesNK JamesNK left a comment

Choose a reason for hiding this comment

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

A few observations on potential issues with the current implementation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
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.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Member

@JamesNK JamesNK left a comment

Choose a reason for hiding this comment

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

The code looks fine to me. I don't really understand this problem space so an SME should also give sign off.

@davidfowl
Copy link
Copy Markdown
Contributor

I'm doing a comparison with the nuget logic in the dotnet-sdk.

Copy link
Copy Markdown
Contributor

@davidfowl davidfowl left a comment

Choose a reason for hiding this comment

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

Review: Comparison with SDK NuGet asset resolution logic

I compared the LayoutCommand approach in this PR against the SDK's asset resolution in dotnet/sdk (ResolvePackageAssets.cs, AssetsFileResolver.cs, DependencyContextBuilder.cs, LockFileExtensions.cs). The approach here is sound.

How the SDK handles this

The SDK resolves two lock file targets from project.assets.json:

  • Compile-time target: lockFile.GetTarget(framework, runtimeIdentifier: null) — no RID
  • Runtime target: lockFile.GetTarget(framework, runtimeIdentifier) — with the project's RID (if specified)

NuGet restore already produces a target with RID-resolved assets baked in. NativeLibraries and RuntimeAssemblies in the runtime target are already correct for the platform.

For framework-dependent apps (no RID), the SDK copies RuntimeTargets preserving the runtimes/{rid}/native/ directory structure. At runtime, the .NET host walks the deps.json runtime fallback graph to pick the right RID folder. The SDK itself never flattens RID-specific assets to root.

Why the LayoutCommand approach diverges — and why that's correct

Aspire's AppHost server loads integration assemblies via flat probing paths, not deps.json-based resolution. It can't rely on the .NET host's RID probing. So the LayoutCommand needs to pre-resolve at layout time what the host would normally do at runtime:

  1. BuildBestRuntimeTargetsByFileName scores each RuntimeTarget by RID proximity using the embedded RuntimeIdentifierGraph.json (same graph the SDK/host uses)
  2. CopyNativeLibraries flattens the best-matching native asset to root, and falls back to the second loop for packages that only ship RID-specific natives (the SqlClient case — NativeLibraries is empty, assets live exclusively in runtimes/{rid}/native/)
  3. CopyRuntimeAssemblies similarly overlays the best RID-specific managed assembly over the generic lib/ one

This correctly mirrors what DependencyContextBuilder.GetRuntimeLibrary() does in the SDK — it groups RuntimeTargets by RID into NativeLibraryGroups/RuntimeAssemblyGroups, and the host resolves them via fallback chains. The LayoutCommand just does this resolution eagerly at file-copy time.

One observation

CopyRuntimeTargets (line 283) copies all runtime targets in structured form (output/runtimes/osx-arm64/native/...), while CopyNativeLibraries also copies the best-match flat to root. The structured copies may be unnecessary for Aspire's probing-path model — they'd only matter if something downstream expects runtimes/{rid}/ layout. Not a bug, just extra disk I/O. Worth a comment explaining whether both are intentionally needed or if the structured copy is defensive/future-proofing.

Verdict

The approach correctly adapts the SDK's RID resolution semantics for Aspire's flat-layout constraint. The use of the SDK's own RuntimeIdentifierGraph.json for fallback matching (rather than a hand-rolled heuristic) is the right call. Test coverage looks solid for the core scenarios.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@sebastienros
Copy link
Copy Markdown
Contributor Author

We don’t need our own RID graph anymore because we stopped doing RID resolution in LayoutCommand. Instead we rely on Nuget restore logic (@eerhardt suggested it).

Before, layout only had generic package data plus runtimeTargets, so it had to answer: “for osx-arm64, which asset should I pick?” That required expanding RID fallbacks itself, which is why we embedded the graph.

Now restore runs with the current RID. NuGet uses its RID graph during restore and writes a RID-specific target like net10.0/osx-arm64 into project.assets.json. That target already contains the resolved runtime and native assets to use.

So layout just does: select target, copy listed files. NuGet owns RID fallback; our code no longer reimplements it.

sebastienros and others added 2 commits March 31, 2026 13:17
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
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.

  • Tests / Hosting-1 / Hosting-1 (windows-latest) - Post-test cleanup steps 'Upload logs, and test results | Copy CLI E2E recordings for upload | Upload CLI E2E recordings | Generate test results summary | Post Checkout code' matched the Windows process initialization failure override allowlist.

sebastienros and others added 5 commits March 31, 2026 14: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>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@sebastienros sebastienros added the Servicing-consider Issue for next servicing release review label Apr 1, 2026
@sebastienros sebastienros changed the title Fix SqlClient runtime asset layout on Unix [release/13/2] Fix SqlClient runtime asset layout on Unix Apr 1, 2026
@sebastienros sebastienros changed the title [release/13/2] Fix SqlClient runtime asset layout on Unix [release/13.2] Fix SqlClient runtime asset layout on Unix Apr 1, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

🎬 CLI E2E Test Recordings — 53 recordings uploaded (commit 115740e)

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
InitTypeScriptAppHost_AugmentsExistingViteRepoAtRoot ▶️ 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
StartAndWaitForTypeScriptSqlServerAppHostWithNativeAssets ▶️ 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 #23827070469

@sebastienros sebastienros requested a review from eerhardt April 1, 2026 16:38
@joperezr joperezr added Servicing-approved Approved for servicing release and removed Servicing-consider Issue for next servicing release review labels Apr 1, 2026
@joperezr joperezr merged commit 87e54d9 into release/13.2 Apr 1, 2026
501 of 504 checks passed
@joperezr joperezr deleted the sebros/fix-sqlserver-macos branch April 1, 2026 19:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Servicing-approved Approved for servicing release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants