Skip to content

Commit 796fe42

Browse files
committed
Add plan to support multi-targeting Roslyn
1 parent 9a9d871 commit 796fe42

File tree

1 file changed

+128
-0
lines changed

1 file changed

+128
-0
lines changed

plan.md

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
## Plan: Multi-Roslyn Configuration Support via Build Script
2+
3+
**TL;DR**: Add a `RoslynVersion` MSBuild property to [src/DocoptNet/DocoptNet.csproj](src/DocoptNet/DocoptNet.csproj) that conditionally switches the `Microsoft.CodeAnalysis.CSharp` reference (3.10.0 baseline → 4.4.0) and defines `ROSLYN4_4`. The default build remains unchanged. A new PowerShell script (`build.ps1`) with `Build`, `Test`, and `Pack` parameter sets orchestrates multi-Roslyn builds. The NuGet package ships both analyzer DLLs: unversioned (baseline) and `roslyn4.4/` (new). A guard target in the `.csproj` prevents packing without the Roslyn 4.4 output.
4+
5+
### Assumptions
6+
7+
- **Roslyn 4.4.0** is the correct target for C# 11 raw/interpolated string literal support (ships with .NET 7 SDK / VS 17.4).
8+
- The `roslyn{version}` analyzer path convention (`analyzers/dotnet/roslyn4.4/cs/`) is supported by .NET SDK 6.0.4+. Users on older SDKs automatically fall back to the unversioned `analyzers/dotnet/cs/` path.
9+
- Only the `netstandard2.0` TFM needs to be built with Roslyn 4.4 (the `analyzers/` DLL). The `lib/` TFMs (`netstandard2.0`, `netstandard2.1`, `net47`) are unaffected.
10+
- The `RoslynVersion` property is **not** passed by default — a bare `dotnet build` produces the baseline. Only the script (or explicit `-p:RoslynVersion=4.4`) triggers the variant build.
11+
- The test project [tests/DocoptNet.Tests/DocoptNet.Tests.csproj](tests/DocoptNet.Tests/DocoptNet.Tests.csproj) will also accept `RoslynVersion` passthrough (since it project-references `DocoptNet.csproj`), enabling Roslyn-4.4-specific test coverage.
12+
- The existing [tests/Integration/run.ps1](tests/Integration/run.ps1) and its wrappers remain unchanged — they already invoke `dotnet pack` and `dotnet test`, and will work with the packed NuGet that contains both analyzer variants.
13+
- PowerShell script execution in CI/local commands can rely on the pinned local tool (`dotnet pwsh`) and therefore assumes `dotnet tool restore` has been run first.
14+
15+
---
16+
17+
**Steps**
18+
19+
### 1. Modify [src/DocoptNet/DocoptNet.csproj](src/DocoptNet/DocoptNet.csproj)
20+
21+
**a) Redirect output paths when `RoslynVersion=4.4`**
22+
23+
Add a `PropertyGroup` conditioned on `$(RoslynVersion)` that overrides `BaseOutputPath` and `BaseIntermediateOutputPath` to isolate the Roslyn 4.4 build artifacts from the default build. Also define `ROSLYN4_4` for conditional compilation.
24+
25+
**b) Split the Roslyn `PackageReference` into two conditions**
26+
27+
Replace the current unconditional `Microsoft.CodeAnalysis.CSharp` 3.10.0 reference with:
28+
- 3.10.0 when `$(RoslynVersion)` is empty or unset (baseline)
29+
- 4.4.0 when `$(RoslynVersion)` is `4.4`
30+
31+
Both retain the existing `Condition="'$(TargetFramework)' != 'net47'"` guard.
32+
33+
**c) Add the Roslyn 4.4 analyzer to pack items**
34+
35+
Add a second `<None Pack="true" PackagePath="analyzers/dotnet/roslyn4.4/cs" />` item pointing to the Roslyn 4.4 build output. Use project-relative MSBuild properties (`$(MSBuildProjectDirectory)`, `$(BaseOutputPath)`, etc.) for robust path construction instead of hard-coded Windows separators.
36+
37+
**d) Add a guard target `_ValidateRoslyn44AnalyzerOutput`**
38+
39+
Runs `BeforeTargets="GenerateNuspec"`. Emits an `<Error>` if the Roslyn 4.4 DLL doesn't exist, with a message like:
40+
41+
> *Roslyn 4.4 analyzer output not found at '...'. Build all Roslyn variants first by running: ./build.ps1 -Pack*
42+
43+
This prevents `dotnet pack` from producing an incomplete NuGet package.
44+
45+
### 2. Create `build.ps1` at the repository root
46+
47+
A single PowerShell script with three parameter sets:
48+
49+
**`Build` (default)**
50+
- Parameters: `-Configuration` (default `Release`)
51+
- Actions:
52+
1. `dotnet build` the solution (all projects, all TFMs, baseline Roslyn 3.10)
53+
2. `dotnet build src/DocoptNet/DocoptNet.csproj -f netstandard2.0 -p:RoslynVersion=4.4` (Roslyn 4.4 variant, single TFM only)
54+
55+
**`Test`**
56+
- Parameters: `-Configuration` (default `Release`), `-NoBuild` switch
57+
- Actions:
58+
1. If not `-NoBuild`: invoke `Build` logic first
59+
2. `dotnet test --no-build` the solution (tests against baseline Roslyn)
60+
3. `dotnet test` the test project with `-p:RoslynVersion=4.4` (tests against Roslyn 4.4 — always built in this step because outputs differ)
61+
62+
`-NoBuild` behavior clarification: this switch applies to baseline solution tests only. The Roslyn 4.4 test pass still performs build work as needed for the alternate output path.
63+
64+
**`Pack`**
65+
- Parameters: `-Configuration` (default `Release`), `-VersionSuffix` (optional)
66+
- Actions:
67+
1. Invoke `Build` logic first
68+
2. `dotnet pack src/DocoptNet/DocoptNet.csproj --no-build` with optional `--version-suffix`
69+
70+
All parameter sets pass `-c $Configuration` through. The script uses `$ErrorActionPreference = 'Stop'` and checks `$LASTEXITCODE` after each `dotnet` invocation.
71+
72+
To make invocation directory-independent, the script should `Push-Location` to `$PSScriptRoot` at startup and `Pop-Location` in a `finally` block so cleanup always runs on success or failure.
73+
74+
### 3. Update [.github/workflows/ci.yml](.github/workflows/ci.yml)
75+
76+
**a) Replace the `Build` step**
77+
78+
Change from `dotnet build --configuration Release` to `dotnet pwsh ./build.ps1 -Configuration Release`.
79+
80+
**b) Replace the `Test` step**
81+
82+
Change from `dotnet test --no-build --configuration Release` to `dotnet pwsh ./build.ps1 -Test -NoBuild -Configuration Release`.
83+
84+
**c) Update the `Pack` step**
85+
86+
The existing pack step has version-suffix logic. Keep that logic but replace the `dotnet pack` invocation with `dotnet pwsh ./build.ps1 -Pack -Configuration Release -VersionSuffix $versionSuffix`. The guard target ensures the Roslyn 4.4 output is present.
87+
88+
**d) Consider Linux builds**
89+
90+
The CI matrix includes `ubuntu-22.04`. The build step there also needs to run the PowerShell script. Since this plan uses `dotnet pwsh`, ensure `dotnet tool restore` runs before Build/Test/Pack steps so the pinned PowerShell tool is available on both platforms.
91+
92+
### 4. Add `.gitignore` entry (if needed)
93+
94+
The redirected output path `src/DocoptNet/bin/roslyn4.4/` and `src/DocoptNet/obj/roslyn4.4/` should already be covered by the existing `bin/` and `obj/` ignore patterns. Verify this.
95+
96+
### 5. Documentation update (independent final step)
97+
98+
After all code/CI/verification work is complete, perform documentation updates as a separate, standalone step:
99+
100+
- Update [README.md](README.md) (or CONTRIBUTING if preferred) with `build.ps1` usage for `Build`, `Test`, and `Pack`.
101+
- Explicitly state that plain `dotnet pack src/DocoptNet/DocoptNet.csproj` is expected to fail the guard unless Roslyn 4.4 artifacts have been built.
102+
- Include cross-platform examples that use `dotnet pwsh ./build.ps1` and note the prerequisite `dotnet tool restore`.
103+
104+
---
105+
106+
**Verification**
107+
108+
1. `dotnet build` at the root still works and produces the baseline build (no regression)
109+
2. `dotnet pack src/DocoptNet/DocoptNet.csproj` **fails** with the guard target error message pointing to `build.ps1`
110+
3. `dotnet pwsh ./build.ps1` builds both Roslyn variants successfully
111+
4. `dotnet pwsh ./build.ps1 -Test` runs tests for both variants
112+
5. `dotnet pwsh ./build.ps1 -Pack` produces a `.nupkg` in `dist/` that contains:
113+
- `lib/netstandard2.0/DocoptNet.dll`
114+
- `lib/netstandard2.1/DocoptNet.dll`
115+
- `lib/net47/DocoptNet.dll`
116+
- `analyzers/dotnet/cs/DocoptNet.dll` (Roslyn 3.10 baseline)
117+
- `analyzers/dotnet/roslyn4.4/cs/DocoptNet.dll` (Roslyn 4.4)
118+
- `build/netstandard2.0/docopt.net.targets`
119+
6. Inspect the package with `dotnet nuget verify` or extract and check folder structure
120+
7. CI workflow passes on both Windows and Linux
121+
8. Documentation is updated only after steps 1–7 are complete (as an independent final step)
122+
123+
**Decisions**
124+
125+
- **Configuration-driven, not multi-project**: avoids code duplication and shared project complexity; the tradeoff is that `dotnet build`/`dotnet pack` alone only handle the baseline
126+
- **Guard target over silent omission**: using `Condition="Exists(...)"` on the pack item would silently skip the Roslyn 4.4 DLL; an explicit `<Error>` is safer and more informative
127+
- **Script at repo root**: `build.ps1` matches convention alongside existing `mark-shipped.ps1`; cross-platform via PowerShell Core (already used in CI)
128+
- **Only `netstandard2.0` for Roslyn 4.4 variant**: the other TFMs are for the runtime library only and don't include generator code, so building them again with Roslyn 4.4 is unnecessary

0 commit comments

Comments
 (0)