Conversation
…ate ref assemblies.
There was a problem hiding this comment.
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 thelib/netstandard2.0known 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/**" | |||
There was a problem hiding this comment.
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).
| applyTo: "**/nuspec,**/build.proj,**/ref/**" | |
| applyTo: "**/*.nuspec,**/build.proj,**/ref/**" |
| 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. |
There was a problem hiding this comment.
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.
| 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. |
|
|
||
| 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. |
There was a problem hiding this comment.
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.
| 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. |
| 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: |
There was a problem hiding this comment.
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.
| 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 |
| - 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. |
There was a problem hiding this comment.
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.
| - 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. |
| ``` | ||
| Microsoft.Data.SqlClient.nupkg | ||
| │ | ||
| ├── ref/ # Compile-time reference assemblies (throw null bodies, no real implementation) |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
This pull request introduces new documentation to clarify the NuGet package structure for
Microsoft.Data.SqlClientand 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:
.github/instructions/nuget-package-structure.instructions.mdto clearly document the folder layout, purpose of each directory, and runtime resolution logic for the NuGet package, including a detailed breakdown ofref/,lib/, andruntimes/folders and a known issue with thelib/netstandard2.0/assembly.Build and packaging modernization plan:
.github/plans/auto-generate-ref-assemblies.mddetailing a phased plan to enable compiler-generated reference assemblies (ProduceReferenceAssembly), create a dedicated project forPlatformNotSupportedExceptionstubs, and update the nuspec to consume new artifact paths.Microsoft.DotNet.ApiCompat.Tool, ensuring auto-generated ref assemblies match the checked-in baseline, with breaking changes detected during CI builds.netstandard2.0ref and lib slots and the transition away from legacy path conventions.