diff --git a/.github/instructions/nuget-package-structure.instructions.md b/.github/instructions/nuget-package-structure.instructions.md new file mode 100644 index 0000000000..bb8ad2403e --- /dev/null +++ b/.github/instructions/nuget-package-structure.instructions.md @@ -0,0 +1,81 @@ +--- +applyTo: "**/nuspec,**/build.proj,**/ref/**" +--- +# 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. + +## Folder Structure + +``` +Microsoft.Data.SqlClient.nupkg +│ +├── ref/ # Compile-time reference assemblies (throw null bodies, no real implementation) +│ ├── 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) +│ │ ├── Microsoft.Data.SqlClient.pdb # Debug symbols +│ │ ├── 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 +│ │ ├── 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 +│ ├── 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 +│ ├── 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) + │ ├── 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. diff --git a/.github/plans/auto-generate-ref-assemblies.md b/.github/plans/auto-generate-ref-assemblies.md new file mode 100644 index 0000000000..e9475bae13 --- /dev/null +++ b/.github/plans/auto-generate-ref-assemblies.md @@ -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: + - `true` (for all TFMs, including net462 which doesn't default to true) + - `true` (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. + +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 `Microsoft.Data.SqlClient is not supported on this platform.` + - Sets `Microsoft.Data.SqlClient` (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: + - 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. + +## 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