Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions .github/instructions/nuget-package-structure.instructions.md
Original file line number Diff line number Diff line change
@@ -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.
---
# NuGet Package Structure — Microsoft.Data.SqlClient

This document describes the folder layout of the generated `Microsoft.Data.SqlClient` NuGet package, what each folder holds, and how runtime resolution works.

## Package Resolution Overview

- **`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?


## Folder Structure

```
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?

│ ├── net462/ # Built from netfx/ref, Windows_NT
│ │ ├── Microsoft.Data.SqlClient.dll # NetFx ref assembly — public API surface for .NET Framework
│ │ └── Microsoft.Data.SqlClient.xml # XML doc comments for IntelliSense
│ ├── net8.0/ # Built from netcore/ref, AnyOS
│ │ ├── Microsoft.Data.SqlClient.dll # NetCore ref assembly — public API surface for .NET 8
│ │ └── Microsoft.Data.SqlClient.xml # XML doc comments for IntelliSense
│ ├── net9.0/ # Built from netcore/ref, AnyOS
│ │ ├── Microsoft.Data.SqlClient.dll # NetCore ref assembly — public API surface for .NET 9
│ │ └── Microsoft.Data.SqlClient.xml # XML doc comments for IntelliSense
│ └── netstandard2.0/ # Built from netcore/ref, AnyOS
│ ├── Microsoft.Data.SqlClient.dll # NetStandard ref assembly — public API surface for netstandard2.0
│ └── Microsoft.Data.SqlClient.xml # XML doc comments 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)
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?

│ │ ├── 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.

│ │ ├── Microsoft.Data.SqlClient.xml # XML doc comments
│ │ └── {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.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.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?

│ ├── net9.0/ # Built from netcore/src, OSGroup=AnyOS
│ │ ├── Microsoft.Data.SqlClient.dll # AnyOS stub — same as net8.0
│ │ ├── 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
│ └── 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.dll # ⚠️ Currently a ref assembly (throw null bodies) — see known issue below
│ ├── Microsoft.Data.SqlClient.pdb # Debug symbols
│ └── 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.

│ ├── net462/
│ │ ├── Microsoft.Data.SqlClient.dll # Full .NET Framework impl (same as lib/net462)
│ │ └── Microsoft.Data.SqlClient.pdb # Debug symbols
│ ├── net8.0/
│ │ ├── Microsoft.Data.SqlClient.dll # Full .NET 8 impl compiled for Windows_NT
│ │ └── Microsoft.Data.SqlClient.pdb # Debug symbols
│ └── net9.0/
│ ├── Microsoft.Data.SqlClient.dll # Full .NET 9 impl compiled for Windows_NT
│ └── Microsoft.Data.SqlClient.pdb # Debug symbols
└── unix/lib/ # Unix/Linux/macOS implementations (managed SNI)
├── net8.0/
│ ├── Microsoft.Data.SqlClient.dll # Full .NET 8 impl compiled for Unix
│ └── Microsoft.Data.SqlClient.pdb # Debug symbols
└── net9.0/
├── Microsoft.Data.SqlClient.dll # Full .NET 9 impl compiled for Unix
└── Microsoft.Data.SqlClient.pdb # Debug symbols
```

## Known Issue: `lib/netstandard2.0/` Assembly

The `lib/netstandard2.0/` DLL is intended to be a `PlatformNotSupportedException` stub for unsupported platforms. However, it is built by the `BuildNetStandard` target, which builds the **ref project** (`netcore/ref/`) with `BuildForLib=true` and `OSGroup=AnyOS`.

The `NotSupported.targets` file is imported, but its logic is gated on `GeneratePlatformNotSupportedAssemblyMessage` being set — a property that only the **src project** (`netcore/src/`) defines. Since the ref project never sets it, the `GenerateNotSupportedSource` target is inert, and the output is just the ref assembly with `throw null` bodies instead of a proper `PlatformNotSupportedException` stub.
78 changes: 78 additions & 0 deletions .github/plans/auto-generate-ref-assemblies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Plan: Auto-Generate Ref Assemblies from Unified Src Project

## Summary

Enable `ProduceReferenceAssembly` in the unified src project so ref assemblies are compiler-generated during normal builds. Create a small dedicated project for `PlatformNotSupportedException` stubs. Use `Microsoft.DotNet.ApiCompat.Tool` to compare auto-generated ref assemblies against the checked-in ref sources as a baseline. Update the nuspec to consume artifacts from the new output paths.

## Phase 1: Enable auto-generated ref assemblies

1. In `src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj`, add two properties:
- `<ProduceReferenceAssembly>true</ProduceReferenceAssembly>` (for all TFMs, including net462 which doesn't default to true)
- `<GenerateDocumentationFile>true</GenerateDocumentationFile>` (so XML docs are generated alongside the build)

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.
Comment on lines +13 to +15
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.
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.

4. Import `tools/targets/TrimDocsForIntelliSense.targets` in the unified src project to trim the XML doc file to only public API content (removing docs for internal/private members that the compiler includes).

## Phase 2: Create a dedicated PNSE stub project

5. Create a new project at `src/Microsoft.Data.SqlClient/notsupported/Microsoft.Data.SqlClient.NotSupported.csproj` targeting `net8.0;net9.0;netstandard2.0`. This project:
- Sets `<GeneratePlatformNotSupportedAssemblyMessage>Microsoft.Data.SqlClient is not supported on this platform.</GeneratePlatformNotSupportedAssemblyMessage>`
- Sets `<AssemblyName>Microsoft.Data.SqlClient</AssemblyName>` (same assembly name as the real one)
- Imports `tools/targets/ResolveContract.targets` and `tools/targets/NotSupported.targets`
- Points `ContractAssemblyPath` to the ref assembly produced by Phase 1 (for net8.0 or net9.0), or adds a `ProjectReference` to the unified src project with `OutputItemType=ResolvedMatchingContract` and `ReferenceOutputAssembly=false`
- GenAPI reads the ref assembly, generates `.notsupported.cs` source with `PlatformNotSupportedException` bodies, and the project compiles it into DLLs for all three TFMs
- Output goes to a well-known path (e.g., `artifacts/Microsoft.Data.SqlClient/{Config}/anyos/`)

6. Update `tools/targets/ResolveContract.targets` if needed — the current `ContractProject` default points to the legacy `netcore/ref/` project. The new PNSE project should either override `ContractProject` to point to the unified src project, or set `ContractAssemblyPath` directly to the Phase 1 ref assembly output.

## Phase 3: API compat checking

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:
Comment on lines +33 to +35
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.
- 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.
Comment on lines +36 to +41
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.

## Phase 4: Update build infrastructure

10. Update `build.proj` targets:
- The `BuildNetCoreAllOS` target's AnyOS invocation (line 232) should build the new PNSE project instead of the legacy `netcore/src` with `OSGroup=AnyOS`
- The `BuildNetStandard` target (line 241-242) should also use the PNSE project instead of the legacy `netcore/ref` with `BuildForLib=true`
- Remove or deprecate the `OSGroup=AnyOS` invocation of `@(NetCoreDriver)` — it's replaced by the PNSE project
- Add a new target (e.g., `BuildNotSupported`) that builds the PNSE project

11. Update `tools/specs/Microsoft.Data.SqlClient.nuspec` source paths:
- **`ref/net462/`**: Change from `artifacts/$ReferenceType$/bin/Windows_NT/{Config}/Microsoft.Data.SqlClient/ref/net462/` → `artifacts/Microsoft.Data.SqlClient/{Config}/windows_nt/net462/ref/`
- **`ref/net8.0/`**: Change from `artifacts/$ReferenceType$/bin/AnyOS/{Config}/Microsoft.Data.SqlClient/ref/net8.0/` → `artifacts/Microsoft.Data.SqlClient/{Config}/windows_nt/net8.0/ref/` (ref assemblies are OS-independent; use Windows build output)
- **`ref/net9.0/`**: Same pattern as net8.0
- **`ref/netstandard2.0/`**: Remove (no separate ref assembly for netstandard2.0 since the unified src project doesn't target it; the PNSE stub in `lib/netstandard2.0/` serves as the netstandard2.0 surface)
- **`lib/net8.0/`** and **`lib/net9.0/`** (AnyOS stubs): Change to point to the PNSE project output
- **`lib/netstandard2.0/`**: Change to point to the PNSE project output
- Keep `lib/net462/`, `runtimes/win/`, and `runtimes/unix/` paths pointing to unified src project output

12. Verify that the `ref/netstandard2.0/` slot in the nuspec is still needed. If the package has `lib/netstandard2.0/` (the PNSE stub), NuGet may need a matching `ref/netstandard2.0/` for compile-time. Options:
- Use the PNSE stub DLL itself as both `ref/` and `lib/` for netstandard2.0 (since it has the right API surface)
- Or produce a ref assembly from the PNSE project too (via `ProduceReferenceAssembly` on the PNSE project)
- Or use the net8.0 ref assembly re-targeted (may cause issues)

## Verification

- Build the unified src project and confirm ref assemblies + XML docs appear at the expected output paths for all three TFMs (net462, net8.0, net9.0)
- Build the PNSE project and confirm it produces `PlatformNotSupportedException` stubs for net8.0, net9.0, and netstandard2.0 — verify by decompiling or instantiating a type
- Run `dotnet apicompat` between the auto-generated ref assemblies and the checked-in baseline — should pass with no breaking changes
- Run `nuget pack` on the updated nuspec and verify the package structure matches the expected layout from `.github/instructions/nuget-package-structure.instructions.md`
- Run existing unit and functional tests to ensure no regressions

## Decisions

- **ProduceReferenceAssembly over GenAPI for ref generation**: Chose compiler-generated ref assemblies because they are guaranteed to match the implementation's public API surface, require no manual maintenance, and preserve XML doc content from the shared `doc/snippets/` files
- **Dedicated PNSE project over extending unified src**: Chose a separate project to keep concerns clean — the src project produces real implementations + ref assemblies, the PNSE project produces stubs. This avoids adding `AnyOS` mode complexity and `netstandard2.0` TFM to the unified src project
- **Checked-in ref sources as baseline**: Keeps the existing hand-maintained ref `.cs` files as the API compat baseline for now, with a planned future migration to comparing against the last published NuGet package
- **Update nuspec paths**: Chose to update the nuspec to point to the unified project's output paths rather than adding copy steps to bridge to the legacy path structure
Loading