Skip to content

Document nuget package structure#3965

Draft
mdaigle wants to merge 1 commit intomainfrom
dev/mdaigle/generate-ref-assemblies
Draft

Document nuget package structure#3965
mdaigle wants to merge 1 commit intomainfrom
dev/mdaigle/generate-ref-assemblies

Conversation

@mdaigle
Copy link
Contributor

@mdaigle mdaigle commented Feb 18, 2026

This pull request introduces new documentation to clarify the NuGet package structure for Microsoft.Data.SqlClient and outlines a plan to modernize the build process by auto-generating reference assemblies and creating dedicated stubs for unsupported platforms. The changes aim to improve package layout transparency and streamline how ref assemblies and runtime stubs are produced and validated.

Documentation improvements:

  • Added .github/instructions/nuget-package-structure.instructions.md to clearly document the folder layout, purpose of each directory, and runtime resolution logic for the NuGet package, including a detailed breakdown of ref/, lib/, and runtimes/ folders and a known issue with the lib/netstandard2.0/ assembly.

Build and packaging modernization plan:

  • Added .github/plans/auto-generate-ref-assemblies.md detailing a phased plan to enable compiler-generated reference assemblies (ProduceReferenceAssembly), create a dedicated project for PlatformNotSupportedException stubs, and update the nuspec to consume new artifact paths.
  • Outlined steps for API compatibility validation using Microsoft.DotNet.ApiCompat.Tool, ensuring auto-generated ref assemblies match the checked-in baseline, with breaking changes detected during CI builds.
  • Provided guidance for updating build targets and nuspec source paths to align with the new output structure, including handling of netstandard2.0 ref and lib slots and the transition away from legacy path conventions.
  • Documented verification steps and rationale behind key decisions, such as preferring compiler-generated ref assemblies and maintaining checked-in ref sources as the API baseline for compatibility checks.

Copilot AI review requested due to automatic review settings February 18, 2026 23:45
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

Adds internal documentation to clarify the current Microsoft.Data.SqlClient NuGet package layout and to outline a phased approach for modernizing ref assembly/stub generation from the unified source project.

Changes:

  • Introduces a NuGet package structure guide describing ref/, lib/, runtimes/ asset roles and current layout, including the lib/netstandard2.0 known issue.
  • Adds a phased plan to enable compiler-produced reference assemblies, introduce a dedicated PlatformNotSupportedException (PNSE) stub project, and wire API-compat validation and nuspec path updates.

Reviewed changes

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

File Description
.github/plans/auto-generate-ref-assemblies.md Plan doc for generating ref assemblies from the unified project, adding a PNSE stub project, and validating API compatibility / updating packaging paths.
.github/instructions/nuget-package-structure.instructions.md Instruction doc describing current .nupkg folder structure, runtime resolution behavior, and the current netstandard2.0 stub issue.

@@ -0,0 +1,81 @@
---
applyTo: "**/nuspec,**/build.proj,**/ref/**"
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

The front-matter applyTo glob includes **/nuspec, but the repo’s nuspec files are *.nuspec (e.g., tools/specs/Microsoft.Data.SqlClient.nuspec). As written, this instruction likely won’t apply to nuspec edits. Consider changing it to something like **/*.nuspec (and keep the existing **/build.proj,**/ref/** as needed).

Suggested change
applyTo: "**/nuspec,**/build.proj,**/ref/**"
applyTo: "**/*.nuspec,**/build.proj,**/ref/**"

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +15
2. Add a post-build target in the unified csproj (or a new `.targets` file imported by it) that copies the ref assembly from `$(IntermediateOutputPath)ref/$(TargetFileName)` to `$(OutputPath)ref/$(TargetFileName)`. The SDK only places the ref DLL in the intermediate `obj/` directory — this copy step puts it alongside the main assembly output at a predictable location like `artifacts/Microsoft.Data.SqlClient/{Config}/{os}/{tfm}/ref/Microsoft.Data.SqlClient.dll`.

3. Also copy the generated XML doc file (`$(OutputPath)$(AssemblyName).xml`) to `$(OutputPath)ref/$(AssemblyName).xml` so the ref assembly has companion IntelliSense XML in the same directory.
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

In Phase 1, the suggested copy destinations use $(OutputPath)ref/..., but in SDK-style multi-target builds $(TargetFramework) is typically appended to the output directory. In the unified project, OutputPath is set to .../{config}/{normalizedOs}/ (no TFM), so copying into $(OutputPath)ref/ would land outside the per-TFM folder and won’t match the example .../{tfm}/ref/... layout. Prefer using $(TargetDir)ref/... (or $(OutputPath)$(TargetFramework)/ref/...) so the ref assets are placed under the actual TFM output directory.

Suggested change
2. Add a post-build target in the unified csproj (or a new `.targets` file imported by it) that copies the ref assembly from `$(IntermediateOutputPath)ref/$(TargetFileName)` to `$(OutputPath)ref/$(TargetFileName)`. The SDK only places the ref DLL in the intermediate `obj/` directory — this copy step puts it alongside the main assembly output at a predictable location like `artifacts/Microsoft.Data.SqlClient/{Config}/{os}/{tfm}/ref/Microsoft.Data.SqlClient.dll`.
3. Also copy the generated XML doc file (`$(OutputPath)$(AssemblyName).xml`) to `$(OutputPath)ref/$(AssemblyName).xml` so the ref assembly has companion IntelliSense XML in the same directory.
2. Add a post-build target in the unified csproj (or a new `.targets` file imported by it) that copies the ref assembly from `$(IntermediateOutputPath)ref/$(TargetFileName)` to `$(TargetDir)ref/$(TargetFileName)`. The SDK only places the ref DLL in the intermediate `obj/` directory — this copy step puts it alongside the main assembly output at a predictable location like `artifacts/Microsoft.Data.SqlClient/{Config}/{os}/{TargetFramework}/ref/Microsoft.Data.SqlClient.dll`.
3. Also copy the generated XML doc file (`$(TargetDir)$(AssemblyName).xml`) to `$(TargetDir)ref/$(AssemblyName).xml` so the ref assembly has companion IntelliSense XML in the same directory.

Copilot uses AI. Check for mistakes.

2. Add a post-build target in the unified csproj (or a new `.targets` file imported by it) that copies the ref assembly from `$(IntermediateOutputPath)ref/$(TargetFileName)` to `$(OutputPath)ref/$(TargetFileName)`. The SDK only places the ref DLL in the intermediate `obj/` directory — this copy step puts it alongside the main assembly output at a predictable location like `artifacts/Microsoft.Data.SqlClient/{Config}/{os}/{tfm}/ref/Microsoft.Data.SqlClient.dll`.

3. Also copy the generated XML doc file (`$(OutputPath)$(AssemblyName).xml`) to `$(OutputPath)ref/$(AssemblyName).xml` so the ref assembly has companion IntelliSense XML in the same directory.
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

Phase 1 step 3 references the generated XML doc file as $(OutputPath)$(AssemblyName).xml, but by default SDK projects emit it into the per-TFM output directory (e.g., $(TargetDir)$(AssemblyName).xml, or use $(DocumentationFile) if set). Using $(OutputPath) directly will likely point at the parent directory and miss the file for multi-target builds.

Suggested change
3. Also copy the generated XML doc file (`$(OutputPath)$(AssemblyName).xml`) to `$(OutputPath)ref/$(AssemblyName).xml` so the ref assembly has companion IntelliSense XML in the same directory.
3. Also copy the generated XML doc file (`$(DocumentationFile)`) to `$(OutputPath)ref/$(AssemblyName).xml` so the ref assembly has companion IntelliSense XML in the same directory.

Copilot uses AI. Check for mistakes.
Comment on lines +33 to +35
7. Add `Microsoft.DotNet.ApiCompat.Tool` to `dotnet-tools.json` as a local dotnet tool. This provides the `dotnet apicompat` command.

8. Create a new MSBuild target (e.g., `ValidateApiCompat` in a `.targets` file or in `build.proj`) that:
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

Step 7 proposes adding ApiCompat to dotnet-tools.json, but there doesn’t appear to be any repo automation restoring/using that manifest today (no references to dotnet-tools.json or dotnet tool restore --tool-manifest in build/pipeline scripts). To avoid a plan that won’t work when followed, consider either (a) switching to the standard tool manifest location (.config/dotnet-tools.json) and documenting dotnet tool restore, or (b) explicitly adding the restore/invocation steps in the proposed ValidateApiCompat target/CI wiring.

Suggested change
7. Add `Microsoft.DotNet.ApiCompat.Tool` to `dotnet-tools.json` as a local dotnet tool. This provides the `dotnet apicompat` command.
8. Create a new MSBuild target (e.g., `ValidateApiCompat` in a `.targets` file or in `build.proj`) that:
7. Add `Microsoft.DotNet.ApiCompat.Tool` to the standard local tool manifest at `.config/dotnet-tools.json`. This provides the `dotnet apicompat` command when `dotnet tool restore` is run.
8. Create a new MSBuild target (e.g., `ValidateApiCompat` in a `.targets` file or in `build.proj`) that:
- Ensures the ApiCompat tool is available by running `dotnet tool restore` (for local development) and by adding a corresponding `dotnet tool restore` step in CI before invoking this target

Copilot uses AI. Check for mistakes.
Comment on lines +36 to +41
- Builds the existing checked-in ref project at `src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj` to produce baseline ref DLLs (for net462, net8.0, net9.0)
- Runs `dotnet apicompat` comparing the auto-generated ref assemblies (from Phase 1) against these baseline DLLs
- Fails the build if breaking changes are detected (new APIs are allowed; removed/changed APIs are errors)
- This target should run as part of CI but be opt-in for local development (e.g., gated on a property like `ValidateApi=true`)

9. The checked-in ref sources at `src/Microsoft.Data.SqlClient/ref/` remain in the repo as the API baseline. They are no longer used for packaging — only for compat validation. Later, these can be replaced with a baseline from the last published NuGet package.
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

Phase 3 step 8 references a baseline ref project at src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj, but that project doesn’t exist in the repo. The checked-in ref projects are src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj (net462) and src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj (net8.0/net9.0/netstandard2.0), with shared ref sources in src/Microsoft.Data.SqlClient/ref/. Updating the plan to point at the actual csproj(s) will make the build steps actionable.

Suggested change
- Builds the existing checked-in ref project at `src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj` to produce baseline ref DLLs (for net462, net8.0, net9.0)
- Runs `dotnet apicompat` comparing the auto-generated ref assemblies (from Phase 1) against these baseline DLLs
- Fails the build if breaking changes are detected (new APIs are allowed; removed/changed APIs are errors)
- This target should run as part of CI but be opt-in for local development (e.g., gated on a property like `ValidateApi=true`)
9. The checked-in ref sources at `src/Microsoft.Data.SqlClient/ref/` remain in the repo as the API baseline. They are no longer used for packaging — only for compat validation. Later, these can be replaced with a baseline from the last published NuGet package.
- Builds the existing checked-in ref projects at `src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj` (net462) and `src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj` (net8.0/net9.0/netstandard2.0) to produce baseline ref DLLs for each TFM
- Runs `dotnet apicompat` comparing the auto-generated ref assemblies (from Phase 1) against these baseline DLLs
- Fails the build if breaking changes are detected (new APIs are allowed; removed/changed APIs are errors)
- This target should run as part of CI but be opt-in for local development (e.g., gated on a property like `ValidateApi=true`)
9. The checked-in ref sources under `src/Microsoft.Data.SqlClient/ref/` (consumed by the `netfx/ref` and `netcore/ref` projects) remain in the repo as the API baseline. They are no longer used for packaging — only for compat validation. Later, these can be replaced with a baseline from the last published NuGet package.

Copilot uses AI. Check for mistakes.
```
Microsoft.Data.SqlClient.nupkg
├── ref/ # Compile-time reference assemblies (throw null bodies, no real implementation)
Copy link
Contributor

Choose a reason for hiding this comment

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

The Abstractions, Azure, and Logging packages don't have a ref/ directory, but they still play nicely with tooling like Intellisense. Are we maintaing these for MDS just to get docs suitable for Intellisense?

├── lib/ # Default runtime assemblies (used when no RID-specific match in runtimes/)
│ ├── net462/ # Built from netfx/src, Windows_NT
│ │ ├── Microsoft.Data.SqlClient.dll # Full .NET Framework implementation (Windows-only)
│ │ ├── Microsoft.Data.SqlClient.pdb # Debug symbols
Copy link
Contributor

Choose a reason for hiding this comment

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

We're removing debug symbols from our .nupkg files for 7.0 since we publish symbols separately already.


- **`ref/`** — Used at **compile time only**. Thin assemblies with `throw null` bodies defining the public API surface. NuGet selects the best-matching TFM.
- **`runtimes/{rid}/lib/`** — Used at **runtime** when the host OS RID matches. Contains full implementations with OS-specific code. These **override** the corresponding `lib/` assemblies.
- **`lib/`** — Used at **runtime** as a **fallback** when no RID-specific match exists in `runtimes/`. For `net462` this is the real implementation. For `net8.0`/`net9.0` these are AnyOS stubs. For `netstandard2.0` this is the only runtime target.
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we add an explanation of what AnyOS means?

├── lib/ # Default runtime assemblies (used when no RID-specific match in runtimes/)
│ ├── net462/ # Built from netfx/src, Windows_NT
│ │ ├── Microsoft.Data.SqlClient.dll # Full .NET Framework implementation (Windows-only)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we have the full .NET Framework implementation in two places? Do we expect there to be .NET Framework runtimes whose RID doesn't match win ? Do we support such RIDs?

│ │ └── {locale}/ # cs, de, es, fr, it, ja, ko, pl, pt-BR, ru, tr, zh-Hans, zh-Hant
│ │ └── Microsoft.Data.SqlClient.resources.dll # Localized satellite resource DLLs
│ ├── net8.0/ # Built from netcore/src, OSGroup=AnyOS
│ │ ├── Microsoft.Data.SqlClient.dll # AnyOS stub — generated via GenAPI/NotSupported.targets
Copy link
Contributor

Choose a reason for hiding this comment

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

What are the circumstances that would cause this DLL to be used at runtime? What are we trying to accomplish here?

│ │ └── Microsoft.Data.SqlClient.resources.dll # Localized satellite resource DLLs
│ ├── net8.0/ # Built from netcore/src, OSGroup=AnyOS
│ │ ├── Microsoft.Data.SqlClient.dll # AnyOS stub — generated via GenAPI/NotSupported.targets
│ │ ├── Microsoft.Data.SqlClient.pdb # Debug symbols
Copy link
Contributor

Choose a reason for hiding this comment

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

Are these symbols for the generated NotSupported code, or the real implementation?

│ │ ├── Microsoft.Data.SqlClient.pdb # Debug symbols
│ │ ├── Microsoft.Data.SqlClient.xml # XML doc comments (from Windows build)
│ │ └── {locale}/ # Localized satellite resource DLLs (from Windows build)
│ │ └── Microsoft.Data.SqlClient.resources.dll
Copy link
Contributor

Choose a reason for hiding this comment

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

Are these the real resources (that the AnyOS stub DLL doesn't use), or just the string "Not supported" in a bunch of languages?

│ │ ├── Microsoft.Data.SqlClient.xml # XML doc comments (from Windows build)
│ │ └── {locale}/ # Localized satellite resource DLLs (from Windows build)
│ │ └── Microsoft.Data.SqlClient.resources.dll
│ └── netstandard2.0/ # Built from netcore/ref with BuildForLib=true, AnyOS
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we omit this and just provide the ref/ DLL and XML? I'm not sure what the purpose of this is.

│ └── Microsoft.Data.SqlClient.xml # XML doc comments
└── runtimes/ # RID-specific runtime assemblies (override lib/ when RID matches)
├── win/lib/ # Windows-specific implementations (native SNI)
Copy link
Contributor

Choose a reason for hiding this comment

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

Fingers crossed we can eliminate the RID-specific DLLs soon. I haven't looked into it deeply, but I suspect we have #if _WINDOWS and #if _UNIX blocks that could become runtime checks at almost zero cost and save on this complexity. We will look into this post 7.0 GA.

@paulmedynski paulmedynski self-assigned this Feb 19, 2026
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.

2 participants

Comments