Skip to content

build(net11): move the repo to net11 and keep net10 package reach - #147

Merged
e128 merged 4 commits into
mainfrom
feature/net11-upgrade-2
Nov 11, 2026
Merged

e128 merged 4 commits into
mainfrom
feature/net11-upgrade-2

Conversation

@e128

@e128 e128 commented Nov 11, 2026

Copy link
Copy Markdown
Owner

Executive Summary

The repository built and tested on net10.0 and SDK 10.0.4xx. The published analyzer package must keep working for a net10.0 consumer while the repository itself moves to .NET 11.

This change moves every project to net11.0 and pins SDK 11.0.100. It leaves the analyzer assemblies on netstandard2.0 with no package dependency, so one build loads in a .NET 10 host and a .NET 11 host. A new fixture proves that reach by consuming the packed package under SDK 10.

The gate is green: shell lint, format, build, and test all pass. The suite reports 5,270 tests passed, 0 failed, 0 skipped, with 0 build warnings and 0 build errors. The net10.0 consumer proof is behavioral, not a measurement: the packaged analyzer reports E128024 in a net10.0 build.

Code Changes

Build and SDK

  • global.json pins SDK 11.0.100 and Directory.Build.props targets net11.0.
  • The dead EnablePreviewFeatures groups are removed. Union types and runtime async are GA.
  • The runtime-async=on group moves to Directory.Build.targets. A .props file evaluates before the project body sets IsTestProject, IsRoslynComponent, or IsBenchmarkProject, so the role conditions read empty there.
  • tests/E128.Analyzers.Tests no longer multi-targets.

Analyzer reach

  • Both analyzer projects pin LangVersion to 14.0. The SDK 10 compiler must build the sources, and latest there is C# 14. A C# 15 form in analyzer source fails CS8652 before the package can load.
  • E128.Analyzers version 2.7.0 states the two-host reach in its description and tags.
  • tests/Net10Consumer is a new fixture. It carries its own global.json (SDK 10), empty Directory.Build.props and .targets so no repository analyzer reaches it, an empty Directory.Packages.props to stop central package management, and a nuget.config that clears the inherited sources and serves E128.* from artifacts/nupkgs. Its source holds one deliberate E128024 violation.

Application code

  • E128.Reference.Core returns GreetingResult, a C# 15 union of Greeting and GreetingError. The web endpoint maps each case and returns 400 for a blank name.
  • The union makes the switch exhaustive at compile time, so a new case is a build failure rather than a fallthrough.

Dependencies

  • Microsoft.Extensions.* and Mvc.Testing move to 11.0.0.
  • Removed unused pins for System.Collections.Immutable, Humanizer.Core, Microsoft.ApplicationInsights, and Microsoft.Extensions.Diagnostics.Testing.
  • System.Security.Cryptography.Pkcs stays at 11.0.0. Removing the pin let a 9.0.6 transitive version through, which carries a high-severity advisory.

Infrastructure

  • Dockerfile moves to 11.0-resolute.
  • ci.yml and publish.yml move to 11.0.x.
  • ci.yml gains the net10-consumer job. It installs both SDK bands, packs the analyzer, builds the fixture, then fails when the log shows CS8032, AD0001, or a load failure, or when E128024 is absent.
  • runtime-matrix.sh now collects every TFM declaration and no longer exits on an empty array under set -u.
  • solution-inventory.sh counts benchmark projects.
  • podman.sh reads the plain Healthy body instead of JSON.
  • test.sh gains --framework.

Documentation

  • lode/dotnet/net11-upgrade.md records the two-band rule, the analyzer reach rules, the union result, and the formatting hazard.
  • plans/roadmap.md records the deferred items.

Tests and Tooling

  • scripts/check.sh --all: shell, format, build, and test all pass.
  • Suite count: 5,270 passed, 0 failed, 0 skipped.
  • Format: scripts/format.sh --check reports no finding.
  • Analyzer release files: analyzer-release-check.sh --json reports clean, with 214 source rules, 0 unshipped, 215 shipped.
  • Analyzer README table: readme-table-diff.sh --analyzer --json reports drift: false.
  • The net10-consumer CI job is new, so this run is its first use.
  • No test was skipped or disabled by this change.

Review Guide

  1. Directory.Build.targets: the runtime-async condition depends on properties set in the project body, which is why it lives in .targets and not .props.
  2. src/E128.Analyzers/E128.Analyzers.csproj and src/E128.Analyzers.CodeFixers/E128.Analyzers.CodeFixers.csproj: the LangVersion pin at 14.0 is deliberate. Changing it to latest breaks the SDK 10 build of the sources.
  3. tests/Net10Consumer/: check that the fixture's Directory.Build.props, Directory.Packages.props, and nuget.config really cut the repository inheritance. The fixture only proves reach if it builds without the repository's own analyzer and package settings.
  4. .github/workflows/ci.yml: the net10-consumer gate asserts a diagnostic and the absence of a load warning. Both halves are needed, because a silent analyzer and a loaded analyzer look the same by exit code.

Rollback

Revert the single commit. The branch carries one commit on top of current main, and no other branch depends on it.

The previous .NET 10 state is the main tip before this commit.

Links

  • Deferred work: plans/roadmap.md, section ".NET 11 Follow-Up".
  • The closed plan and its evidence record: plans/net11-upgrade/, recoverable from commit 2d1d3c1 on this branch's ancestor history.
  • An earlier push of this branch, origin/feature/net11-upgrade, holds plan documents from 2026-11-06 that exist nowhere else. This pull request does not touch that branch.

🤖 Generated with Claude Code

e128 added 4 commits November 10, 2026 18:44
Move every project to net11.0 and pin SDK 11.0.100, while the published
analyzer keeps reaching a net10.0 consumer from one netstandard2.0 build.

- Directory.Build.props targets net11.0 and drops the dead preview-feature
  groups. The runtime-async switch moves to Directory.Build.targets, because
  the role markers are not set when a .props file evaluates.
- Both analyzer projects pin LangVersion 14.0. The SDK 10 compiler must build
  the sources, and latest there is C# 14, so a C# 15 form fails CS8652
  before the package loads.
- tests/Net10Consumer is a new fixture with its own global.json, props, and
  nuget.config. It carries one deliberate E128024 violation, so the new
  net10-consumer CI job asserts the packaged analyzer loaded and reported.
- The analyzer test project no longer multi-targets. The repository builds
  and tests on net11.0 only.
- E128.Analyzers 2.7.0 states the net10 and net11 host reach in its package
  metadata and tags.
- Microsoft.Extensions packages and Mvc.Testing move to 11.0.0. Unused pins
  for System.Collections.Immutable, Humanizer.Core,
  Microsoft.ApplicationInsights, and Microsoft.Extensions.Diagnostics.Testing
  are removed.
- E128.Reference.Core returns GreetingResult, a C# 15 union of Greeting and
  GreetingError, and the web endpoint maps each case.
- Containers move to 11.0-resolute. CI and publish move to 11.0.x.
- runtime-matrix.sh collects every TFM declaration and no longer trips on an
  empty array. solution-inventory.sh counts benchmarks. podman.sh reads the
  plain health body. test.sh gains --framework.
- Lode records the net11 practices. The net11-upgrade plan closes.

Co-Authored-By: Claude Code

Co-Authored-By: Claude
The root .globalconfig escalates every analyzer diagnostic to error, so the
deliberate E128024 probe failed the fixture build instead of reporting. The
net10-consumer job then stopped before its gate could run.

Add a fixture-local .globalconfig at global_level 102 that sets the analyzer
default severity to warning. The probe still appears in the build log, the
build exits 0, and any real compile error or analyzer load failure still fails
the job.

Verified with SDK 10.0.404: dotnet msbuild -t:Rebuild reports one
warning E128024 and exits 0.

Co-Authored-By: Claude Code

Co-Authored-By: Claude
The previous attempt lowered the analyzer default severity so the fixture's
deliberate E128024 violation would warn instead of fail. That weakened the rule
and still failed, because the gate read net10-consumer.log from the repository
root while the build wrote it inside tests/Net10Consumer.

E128024 stays at error severity. The fixture source now carries XML doc
comments, so it compiles cleanly under the repository analyzer rules.

The proof moves to the analyzer report. The CI build passes ReportAnalyzer=true
with detailed verbosity, and the report lists every rule that executed. The job
fails when the report omits E128024 or when the log carries a load warning. The
log now writes to the repository root, where the gate reads it.

Co-Authored-By: Claude Code

Co-Authored-By: Claude
A _ arm accepts any case type added later, so the compiler stops warning about
a missing case. The null arm names the empty case and keeps the check.

Delete the Match wrapper, which only invoked its argument, and inline the
switch at each call site.

SS019 demands a default label, which a union switch must not have, so the two
files that switch over a union take a scoped none row in .editorconfig.

Co-Authored-By: Claude
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.

1 participant