Skip to content

Remove SourceGenerator.Foundations, ship analyzers under Roslyn-versioned folders for VS2022 and VS2026, and validate the packages - #4120

Merged
marcschier merged 9 commits into
masterfrom
marcschier/remove-source-generator-foundations
Jul 31, 2026
Merged

marcschier merged 9 commits into
masterfrom
marcschier/remove-source-generator-foundations

Conversation

@marcschier

@marcschier marcschier commented Jul 30, 2026 •

Copy link
Copy Markdown
Collaborator

Description

The two OPC UA source generators were built on SourceGenerator.Foundations (SGF), whose only job was getting the generator's dependency closure loaded inside the compiler. It does so with a process-wide, first-generator-wins AppDomain.AssemblyResolve hook backed by a non-concurrent Dictionary mutated from AssemblyLoad callbacks. Concurrent compilations in a shared VBCSCompiler race it, which is the root cause of the intermittent clean-build failure:

CSC : error CS8784: Generator 'StackSourceGeneratorHoist' failed to initialize. ...
Exception was of type 'FileNotFoundException' with message 'Could not load file or assembly
'SourceGenerator.Foundations.Contracts, Version=2.0.14.0, ...'

The repository already solves that problem deterministically — #4041 made the NuGet packages ship the full closure under analyzers/dotnet/.../cs, and Directory.Build.targets registers the same closure as Analyzer items for in-repo ProjectReference consumers. SGF was therefore pure liability and is removed.

What changed

1. SourceGenerator.Foundations removed. ModelSourceGenerator and StackSourceGenerator are plain [Generator(LanguageNames.CSharp)] IIncrementalGenerator implementations again. SGF's implicit crash handling is replaced by an explicit SourceGenerator.Guard that reports MODELGEN003 / STACKGEN003 and rethrows cancellation. Its logger sink was already dead code and is dropped. Opc.Ua.SourceGeneration.Stack.dll shrank from 18 MB (embedded closure) to 29 KB.

2. Analyzers now ship under Roslyn-versioned folders. analyzers/dotnet/roslyn4.14/cs and analyzers/dotnet/roslyn5.0/cs instead of analyzers/dotnet/cs, driven by roslyn.props at the repository root, with new *.Pack projects owning the package identity. The versioned folder matters: a host older than the band skips the analyzer instead of loading it and failing inside generator initialization.

3. Dependencies flow automatically. Both delivery paths discover the payload by globbing the generator's output directory, so adding a package to a generator's dependency tree requires no .csproj, .targets, .nuspec or validation-script edit. Verified by temporarily adding a PackageReference and watching it appear in the payload with no other file touched.

Two Roslyn bands: VS2022 and VS2026

Analyzers ship twice, once per band. The .NET SDK loads the highest band its compiler supports and silently skips anything above it, so a single-band package leaves every consumer below that band with no diagnostics and no generated code.

Band Built against Loaded by
roslyn4.14 Microsoft.CodeAnalysis.CSharp 4.14.0 Visual Studio 2022 17.14+ / .NET 9 SDK
roslyn5.0 Microsoft.CodeAnalysis.CSharp 5.0.0 Visual Studio 2026 18.0+ / .NET 10 SDK

Getting this wrong is invisible, which is most of why it took three attempts. The rule:

An analyzer must reference the lowest System.Collections.Immutable / System.Reflection.Metadata across all hosts it claims to support, and must never ship them. .NET binds a reference upward but never downward.

Every failure mode is a compiler warning:

Diagnostic Cause
CS9057 Built against a newer Roslyn than the host, so it is skipped entirely
CS8784 Loaded, then MissingMethodException - a shipped S.C.I bound a second ImmutableArray<T>
CS8032 Analyzer instance could not be created

This PR first shipped a roslyn4.8 band that never loaded - confirmed against a real Roslyn 4.8 compiler:

warning CS8784: Generator 'ModelSourceGenerator' failed to initialize ...
MissingMethodException: Method not found:
'IncrementalValueProvider`1<ImmutableArray`1<!!0>>
 IncrementalValueProviderExtensions.Collect(IncrementalValuesProvider`1<!!0>)'

That band was removed, and this is not a regression: master had the same defect, pinned to Roslyn 4.14 while referencing System.Collections.Immutable 10.0.0.0, so no 4.x host ever worked there either.

The band that landed instead is 4.14, and it is nearly free: Microsoft.CodeAnalysis 4.14 depends on the same S.C.I 9.0.0 as 5.0.0 and its csc runs on net9.0. Both bands therefore share $(RoslynRuntimeVersion), so Opc.Ua.Types and Opc.Ua.SourceGeneration.Core are compiled once and serve both; only the five Roslyn-referencing assemblies get a second build, each from a 12-line project that glob-links the primary sources and overrides RoslynApiVersion. Going lower still - 4.8 wants S.C.I 7.x - would mean rebuilding that whole closure, Opc.Ua.Types included, and remains out of scope. (System.Collections.Immutable 7.0.0 also does not resolve in this repository: NU1603, 8.0.0 substituted.)

Nothing else in the repository exercises the down-level payload, and since every failure is a warning, a broken band would ship silently - exactly as the 4.8 one did. So validate-source-generator-packages.ps1 now restores a real Microsoft.Net.Compilers.Toolset 4.14.0, runs its csc.dll over the packed roslyn4.14 payload, and asserts both that none of CS9057/CS8784/CS8032/CS8034 is emitted and that /reportanalyzer lists the generator as having executed - silence alone would also pass if the generator were never handed to the compiler. The migration analyzer, whose payload comes from a hand-written nuspec rather than SourceGeneratorPack.targets, gets the same check plus a band/assembly manifest assertion.

To stop the trap recurring, Microsoft.CodeAnalysis*, System.Collections.Immutable and System.Reflection.Metadata are excluded from both the package payload and the in-repo analyzer closure, and asserted against in the validation script. roslyn.props and docs/DeveloperGuide.md document what adding a band requires.

The shipped package set is unchanged

Evaluating PackageId/IsPackable across every project in preview-pack.slnx gives the same 47 package ids on master and on this branch, and a full Release build plus dotnet pack --no-build produces exactly those 47. Only the owning project changed:

Package master this PR
…Opc.Ua.SourceGeneration Opc.Ua.SourceGeneration.csproj Opc.Ua.SourceGeneration.Pack.csproj
…Opc.Ua.SourceGeneration.Stack Opc.Ua.SourceGeneration.Stack.csproj Opc.Ua.SourceGeneration.Stack.Pack.csproj

.azurepipelines/expected-packages.txt pins the expected set and the validation script fails when the packed output drifts.

Two new package checks

build/<PackageId>.props naming. NuGet only auto-imports that exact name, and the file declares every CompilerVisibleProperty / CompilerVisibleItemMetadata the model generator relies on. It was previously packed as build/OPCFoundation.Opc.Ua.SourceGeneration.props, which does not match the package id — so no NuGet consumer ever imported it, and every ModelSourceGenerator* property and AdditionalFiles metadata was silently invisible. In-repo projects were unaffected because they import it by relative path.

An end-to-end consumer that actually drives the generator. The pre-existing clean-consumer check only proved the analyzer loads. Test-SourceGeneratingConsumer builds a standalone project that feeds a real NodeSet2 in as an AdditionalFile, pins a ModelSourceGeneratorPrefix that cannot be derived from the model, and references the emitted types from hand-written code. It deliberately does not import the props by path, so the package's auto-import is what is under test. Both checks were verified to fail against a package rebuilt with the misnamed props.

CI: parallelism re-enabled

buildandtest.yml pinned three dotnet build invocations to /p:UseSharedCompilation=false and -maxcpucount:1. dotnet build defaults to all logical processors, so -maxcpucount:1 actively disabled parallelism. Both flags were introduced silently in unrelated PRs (f358078b1, 208c601b0) and suppress exactly the conditions the SGF race needed — now structurally gone. Compilation is ~72 % of what that workflow executes (233 of 323 job-minutes in run 29916768976), so the queue-independent Build step durations are the baseline:

Measurement Baseline
Build step, 26 test-* jobs mean 5.53 min, sum 143.9 min
build-linux-all-tfm 56.3 min
build-windows-all-tfm 32.5 min

codeql-analysis.yml keeps the flag — the CodeQL tracer requires it.

Incidental fixes

  • Microsoft.CodeAnalysis.CSharp declares an exact [version] range on Microsoft.CodeAnalysis.Common, so with CentralPackageTransitivePinningEnabled the central pins must move in lockstep or restore fails with NU1109. They now derive from $(RoslynApiVersionVS2026).
  • MigrationGeneratorTests created its generator driver without parse options — silently fine under Roslyn 4.14, Inconsistent language versions under 5.0.
  • The migration analyzer props injected <Analyzer> items by hand from analyzers/dotnet/cs; NuGet registers analyzers itself and selects the matching folder, so the injection was obsolete and would now force the wrong assembly to load.
  • Exception diagnostics reported ex.StackTrace (null for an exception that never left its throw site, and carrying neither type nor inner chain) — now ex.ToString() at all three call sites.
  • The pack guard tested the flattened assembly list, so it only fired when no variant produced output; it now checks each declared variant contributed assemblies.
  • GeneratePackageOnBuild is disabled for the generators; packing happens in CI or on an explicit dotnet pack.

Note for reviewers

Building this repository requires a Roslyn 5.x host (the .NET 10 SDK or Visual Studio 2026). That is already the documented prerequisite and every CI leg installs 10.0.x last, but it is now a hard requirement.

Related Issues

No tracking issue: this is a follow-up to the CS8784 clean-build failure reported against #4041, which fixed the packaged delivery path but left the in-repo project-reference path relying on the SGF resolver.

Checklist

Put an x in the boxes that apply. You can complete these step by step after opening the PR.

  • I have signed the CLA and read the CONTRIBUTING doc.
  • I have added tests that prove my fix is effective or that my feature works and increased code coverage.
  • I have added all necessary documentation.
  • I have verified that my changes do not introduce (new) build or analyzer warnings.
  • I ran all tests locally using the UA.slnx solution against at least .net framework and .net 10, and all passed.
  • I fixed all failing and flaky tests in the CI pipelines and all CodeQL warnings.
  • I have addressed all PR feedback received.

Verification detail

Check Result
dotnet build UA.slnx (clean, and after merging master) 0 errors; 18 warnings, all pre-existing CA1873 in Opc.Ua.Client
Opc.Ua.SourceGeneration.Tests 81/81 on net10.0 and net48
Opc.Ua.SourceGeneration.Stack.Tests 92/92 on net10.0, 89/89 on net48
Opc.Ua.SourceGeneration.Core.Tests 3722 passed, 8 skipped
Opc.Ua.MigrationAnalyzer.Tests 121/121 on net10.0 and net48
Package id set, master vs branch identical, 47 packages
Release build + dotnet pack --no-build 47 packages, matching the manifest; both roslyn4.14 and roslyn5.0 present; no host-provided assemblies shipped
validate-source-generator-packages.ps1 passes, including manifest, props-naming, end-to-end generator consumer and down-level host checks
Down-level band under real csc 4.14.0 all three analyzer packages load and run
Negative tests misnamed props → both checks fail as designed; hidden variant output → pack guard fires; 5.0 build placed in roslyn4.14 → real CS9057; non-running generator name → execution assertion fires; expecting a roslyn4.8 band → manifest check fires

…022/VS2026

The two OPC UA source generators were built on SourceGenerator.Foundations,
whose only job was getting the generator's dependency closure loaded inside the
compiler. It did so with a process-wide, first-generator-wins
AppDomain.AssemblyResolve hook backed by a non-concurrent Dictionary mutated
from AssemblyLoad callbacks, which raced on parallel clean builds and failed
with CS8784 ("Generator 'StackSourceGeneratorHoist' failed to initialize ...
Could not load file or assembly 'SourceGenerator.Foundations.Contracts'").

The repository already solves that problem deterministically: the NuGet
packages ship the closure under analyzers/dotnet/.../cs and Directory.Build.targets
registers the same closure as Analyzer items for project-reference consumers.
SourceGenerator.Foundations is therefore removed and both generators are plain
Roslyn IIncrementalGenerator implementations again. Its implicit crash handling
is replaced by an explicit SourceGenerator.Guard that reports MODELGEN003 /
STACKGEN003 and rethrows cancellation.

Analyzers and generators are now built once per supported Roslyn API version so
they load on both Visual Studio 2022 and Visual Studio 2026. tools/RoslynVariants.props
is the single source of truth for the versions; each analyzer has a primary
project (Roslyn 5.0), a *.Roslyn4_8 sibling that glob-links the same sources
(Roslyn 4.8), and - for the source generators - a *.Pack project that ships both
under analyzers/dotnet/roslyn<major>.<minor>/cs. Opc.Ua.MigrationAnalyzer,
.CodeFixer and .Generator get the same treatment.

Both delivery paths discover the payload by globbing the generator's output
directory, so adding a package to a generator's dependency tree needs no project
file, nuspec or validation script change.

Also fixed while migrating:

- Microsoft.CodeAnalysis.CSharp declares an exact [version] range on
  Microsoft.CodeAnalysis.Common, so with central transitive pinning the pins must
  move in lockstep or restore fails with NU1109. The central pins now derive from
  the Visual Studio 2026 Roslyn version.
- LanguageVersion.CSharp13 does not exist in Roslyn 4.8; the stack generator now
  uses CompilationOptions.IsCSharp13OrLater.
- MigrationGeneratorTests created its generator driver without parse options,
  which silently matched under Roslyn 4.14 but throws "Inconsistent language
  versions" under 5.0.
- build/OPCFoundation.Opc.Ua.SourceGeneration.props was packed under a name that
  did not match the package id, so NuGet never auto-imported it. It is now packed
  as build/$(PackageId).props.
- The migration analyzer props injected Analyzer items by hand from
  analyzers/dotnet/cs. NuGet registers analyzers itself and selects the matching
  Roslyn folder, so the injection was obsolete and would now force the wrong
  variant to load alongside the selected one.
- GeneratePackageOnBuild is disabled for the generators; packing happens in CI or
  on an explicit dotnet pack.

Because the repository's own projects consume the Visual Studio 2026 variant,
building this repository now requires a Roslyn 5.x host (the .NET 10 SDK or
Visual Studio 2026), which is already the documented prerequisite.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9ed53262-5cfa-429a-820e-bf6146a00d8b
Copilot AI review requested due to automatic review settings July 30, 2026 11:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Removes SourceGenerator.Foundations from the OPC UA source generators and restructures analyzers/generators to ship/build per supported Roslyn API version (VS2022 + VS2026), including updated packaging and in-repo consumption to avoid CS8784 clean-build races.

Changes:

  • Introduces shared MSBuild targets/props for Roslyn-variant builds and multi-folder analyzer packaging (roslyn4.8 / roslyn5.0).
  • Refactors generators back to plain Roslyn IIncrementalGenerator with explicit exception-to-diagnostic guarding.
  • Updates MigrationAnalyzer + docs/tests/pipelines to the new multi-Roslyn layout and dependency-flow model.

Reviewed changes

Copilot reviewed 56 out of 56 changed files in this pull request and generated no comments.

Show a summary per file
File Description
tools/SourceGeneratorVariant.targets Shared build settings for generator variant projects (per Roslyn API version).
tools/SourceGeneratorPack.targets Shared packing logic to ship multiple Roslyn variant analyzer folders.
tools/SourceGeneration.slnx Adds Roslyn4_8 + Pack projects to the source generation solution.
tools/RoslynVariants.props Single source of truth for supported Roslyn API versions + folder names.
tools/Opc.Ua.SourceGeneration/SourceGeneratorTelemetry.cs Removes SGF logging bridge; keeps only Debug sink.
tools/Opc.Ua.SourceGeneration/SourceGeneratorPackaging.targets Removes legacy packaging target (single-folder analyzers/dotnet/cs flow).
tools/Opc.Ua.SourceGeneration/SourceGeneratorHelpers.cs Adds shared SourceGenerator.Guard + debugger attach helper.
tools/Opc.Ua.SourceGeneration/SourceGenerator.cs Makes SourceGenerator partial to share helper implementations.
tools/Opc.Ua.SourceGeneration/Opc.Ua.SourceGeneration.csproj Converts to primary (VS2026) variant project importing shared sources/targets.
tools/Opc.Ua.SourceGeneration/Opc.Ua.SourceGeneration.Sources.targets Centralizes identity/sources for variant projects + imports variant settings.
tools/Opc.Ua.SourceGeneration/NugetREADME.md Documents supported hosts by Roslyn API.
tools/Opc.Ua.SourceGeneration/ModelSourceGenerator.cs Drops SGF base; uses Roslyn generator + guarded callbacks.
tools/Opc.Ua.SourceGeneration/ModelCompilation.cs Removes SGF logger/context types; telemetry now uses Roslyn context.
tools/Opc.Ua.SourceGeneration/DataTypeCompilation.cs Removes SGF context alias.
tools/Opc.Ua.SourceGeneration/CompilationOptions.cs Adds IsCSharp13OrLater helper (no LanguageVersion.CSharp13 dependency).
tools/Opc.Ua.SourceGeneration.Stack/StackSourceGenerator.cs Drops SGF base; uses Roslyn generator + guarded callback.
tools/Opc.Ua.SourceGeneration.Stack/StackGeneration.cs Removes SGF logger/context types; updates C# version check helper usage.
tools/Opc.Ua.SourceGeneration.Stack/SourceGenerator.cs Makes SourceGenerator partial to share helper implementations.
tools/Opc.Ua.SourceGeneration.Stack/Opc.Ua.SourceGeneration.Stack.csproj Converts to primary (VS2026) variant project importing shared sources/targets.
tools/Opc.Ua.SourceGeneration.Stack/Opc.Ua.SourceGeneration.Stack.Sources.targets Centralizes stack generator identity/sources + imports variant settings.
tools/Opc.Ua.SourceGeneration.Stack/NugetREADME.md Documents supported hosts by Roslyn API.
tools/Opc.Ua.SourceGeneration.Stack.Roslyn4_8/Opc.Ua.SourceGeneration.Stack.Roslyn4_8.csproj Adds VS2022 (Roslyn 4.8) stack variant project.
tools/Opc.Ua.SourceGeneration.Stack.Pack/Opc.Ua.SourceGeneration.Stack.Pack.csproj Adds pack project to ship both stack variants.
tools/Opc.Ua.SourceGeneration.Roslyn4_8/Opc.Ua.SourceGeneration.Roslyn4_8.csproj Adds VS2022 (Roslyn 4.8) model variant project.
tools/Opc.Ua.SourceGeneration.Pack/Opc.Ua.SourceGeneration.Pack.csproj Adds pack project to ship both model variants + renamed auto-import props.
tools/Opc.Ua.MigrationAnalyzer/Opc.Ua.MigrationAnalyzer.nuspec Ships analyzer/codefix/generator into per-Roslyn folders.
tools/Opc.Ua.MigrationAnalyzer/Opc.Ua.MigrationAnalyzer.csproj Pins to Roslyn variant infra; builds both variants before pack; sets nuspec props.
tools/Opc.Ua.MigrationAnalyzer/Opc.Ua.MigrationAnalyzer.Sources.targets Centralizes migration analyzer identity/sources + imports variant settings.
tools/Opc.Ua.MigrationAnalyzer/OPCFoundation.NetStandard.Opc.Ua.MigrationAnalyzer.props Removes manual <Analyzer> injection to avoid double-loading variants.
tools/Opc.Ua.MigrationAnalyzer/NugetREADME.md Updates packaging notes to per-Roslyn folder layout.
tools/Opc.Ua.MigrationAnalyzer.Roslyn4_8/Opc.Ua.MigrationAnalyzer.Roslyn4_8.csproj Adds VS2022 (Roslyn 4.8) migration analyzer variant.
tools/Opc.Ua.MigrationAnalyzer.Generator/Opc.Ua.MigrationAnalyzer.Generator.csproj Converts generator to variant infra via shared sources/targets.
tools/Opc.Ua.MigrationAnalyzer.Generator/Opc.Ua.MigrationAnalyzer.Generator.Sources.targets Centralizes generator identity/sources + imports variant settings.
tools/Opc.Ua.MigrationAnalyzer.Generator.Roslyn4_8/Opc.Ua.MigrationAnalyzer.Generator.Roslyn4_8.csproj Adds VS2022 (Roslyn 4.8) migration generator variant.
tools/Opc.Ua.MigrationAnalyzer.CodeFixer/Opc.Ua.MigrationAnalyzer.CodeFixer.csproj Converts code fixer to variant infra via shared sources/targets.
tools/Opc.Ua.MigrationAnalyzer.CodeFixer/Opc.Ua.MigrationAnalyzer.CodeFixer.Sources.targets Centralizes codefix identity, linked sources, and Roslyn package overrides.
tools/Opc.Ua.MigrationAnalyzer.CodeFixer.Roslyn4_8/Opc.Ua.MigrationAnalyzer.CodeFixer.Roslyn4_8.csproj Adds VS2022 (Roslyn 4.8) migration code fixer variant.
tools/MigrationAnalyzerVariant.targets Shared build settings for migration-analyzer variant projects.
tools/MigrationAnalyzer.slnx Adds Roslyn4_8 and pack projects to the migration analyzer solution.
tests/Opc.Ua.SourceGeneration.Tests/TypeGeneratorTests.cs Removes SGF hoist usage; uses generator directly and parse options.
tests/Opc.Ua.SourceGeneration.Tests/NodesetMethodArgumentTests.cs Removes SGF hoist usage; uses generator directly.
tests/Opc.Ua.SourceGeneration.Tests/NodesetIdentifierSidecarTests.cs Removes SGF hoist usage; uses generator directly.
tests/Opc.Ua.SourceGeneration.Tests/NodesetEventRecordTests.cs Removes SGF hoist usage; uses generator directly.
tests/Opc.Ua.SourceGeneration.Tests/ModelGeneratorTests.cs Removes SGF hoist usage; uses generator directly.
tests/Opc.Ua.SourceGeneration.Tests/ModelDependencyScannerTests.cs Removes SGF hoist usage; uses generator directly.
tests/Opc.Ua.SourceGeneration.Stack.Tests/StackGeneratorTests.cs Removes SGF hoist usage; uses generator directly.
tests/Opc.Ua.SourceGeneration.Stack.Tests/StackGenerationAssemblyTests.cs Removes SGF hoist usage; uses generator directly.
tests/Opc.Ua.SourceGeneration.Stack.Tests/SourceGeneratorDiagnosticTests.cs Adds tests for SourceGenerator.Guard behavior.
tests/Opc.Ua.MigrationAnalyzer.Tests/Generators/MigrationGeneratorTests.cs Fixes driver creation to pass parse options for Roslyn 5.x consistency.
docs/migrate/2.0.x/packages.md Removes SGF from migration package list.
docs/DeveloperGuide.md Documents new per-Roslyn analyzer/generator build & packaging model.
UA.slnx Adds Roslyn4_8 and pack projects to main solution layout.
Directory.Packages.props Central Roslyn package pins derive from RoslynApiVersionVS2026; removes SGF pin.
Directory.Build.targets Adds target to include generator runtime closure as Analyzer items for project-ref usage.
.azurepipelines/validate-source-generator-packages.ps1 Validates multi-Roslyn analyzer folders and forbids SGF/Microsoft.CodeAnalysis shipping.
.azurepipelines/preview.yml Updates pipeline comments for new nuspec property names.
Comments suppressed due to low confidence (4)

tools/SourceGeneratorPack.targets:1

  • The property name appears to be misspelled (NoRoslynatorAnalzers). If the repo/tooling expects NoRoslynatorAnalyzers, this typo will prevent the intended setting from taking effect. Rename the property to the correct spelling to ensure consistent analyzer suppression.
    tools/SourceGeneratorPack.targets:1
  • Setting PackageLicenseFile to an empty value can cause NuGet packing metadata warnings/misconfiguration. If you’re using PackageLicenseExpression, remove PackageLicenseFile entirely, or set it to an actual file that is packed into the package.
    tools/Opc.Ua.SourceGeneration/SourceGeneratorTelemetry.cs:1
  • The exception argument is currently not included in the output, which drops the most useful debugging context when logging failures. Consider appending exception (e.g., exception.ToString()) to the Debug.WriteLine output when it is non-null.
    tools/Opc.Ua.SourceGeneration/SourceGeneratorHelpers.cs:1
  • Reporting only ex.Message + ex.StackTrace can produce diagnostics that lack the exception type and inner-exception chain, and StackTrace may be null. Consider reporting ex.ToString() (or including ex.GetType().FullName + ex.ToString()) as a single diagnostic argument (optionally truncated) so the diagnostic is reliably actionable across exception types.

Audit of the packages preview.yml produces, plus the CI parallelism that the
SourceGenerator.Foundations removal unblocks.

Package audit: moving the two generator packages onto dedicated *.Pack projects
leaves the shipped set unchanged. Evaluating PackageId/IsPackable across every
project in preview-pack.slnx yields the same 47 package ids on master and on this
branch, and a full Release build + `dotnet pack --no-build` produces exactly those
47 packages. Only the owning project changed, which is invisible to consumers.

To keep it that way, .azurepipelines/expected-packages.txt pins the expected set
and validate-source-generator-packages.ps1 fails when the packed output does not
match, so adding, removing or renaming a shipped package has to happen
deliberately in the same pull request.

The script gains two further checks:

- The model generator's props file has to be named build/<PackageId>.props.
  NuGet only auto-imports that exact name, and the file declares every
  CompilerVisibleProperty and CompilerVisibleItemMetadata the generator relies on.
  Until this branch it was packed as build/OPCFoundation.Opc.Ua.SourceGeneration.props,
  which does not match the package id OPCFoundation.NetStandard.Opc.Ua.SourceGeneration,
  so no NuGet consumer ever imported it and every ModelSourceGenerator* property and
  AdditionalFiles metadata was silently invisible to the generator. In-repo projects
  were unaffected because they import the props by relative path.

- A standalone consumer that actually drives the generator. The existing clean
  consumer only proves the analyzer loads; this one feeds a real NodeSet2 in as an
  AdditionalFile, pins a ModelSourceGeneratorPrefix that cannot be derived from the
  model itself, and then references the emitted identifier tables and NodeState type
  from hand-written code. A generator that does not run, or runs without seeing its
  options, becomes a compile error. Deliberately does not import the props by path,
  so the package's auto-import is what is under test. Both new checks were verified
  to fail against a package rebuilt with the historical misnamed props.

CI: remove /p:UseSharedCompilation=false and -maxcpucount:1 from buildandtest.yml.
`dotnet build` defaults to all logical processors, so -maxcpucount:1 actively
disabled parallelism. Both flags were added silently in unrelated pull requests
(f358078, 208c601) and suppress exactly the conditions the SourceGenerator.
Foundations resolver race needed; that race is gone, and disabling the compiler
server now costs more than before because the analyzer closure registered per
compilation grew to 19 assemblies. Compilation is roughly 72 percent of what the
workflow executes (233 of 323 job-minutes in run 29916768976), so the Build step
durations there are the baseline to compare against. codeql-analysis.yml keeps the
flag because the CodeQL tracer needs it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9ed53262-5cfa-429a-820e-bf6146a00d8b
@marcschier marcschier changed the title Remove SourceGenerator.Foundations and multi-target analyzers for VS2022/VS2026 Remove SourceGenerator.Foundations, multi-target analyzers for VS2022/VS2026, and validate the shipped packages Jul 30, 2026
…ostics

Copilot's review left no inline threads, only four low-confidence notes. Two were
valid and are fixed; two describe load-bearing settings and are now documented so
they are not "fixed" into breakage later.

Fixed:

- SourceGeneratorTelemetry dropped the exception when a log entry had no matching
  diagnostic descriptor. The SGF-based code passed it to the external sink, so
  removing SGF silently lost it. The debug fallback now appends the exception.
  Uses a literal separator rather than Environment.NewLine, which RS1035 bans in
  analyzers.

- Exception diagnostics (MODELGEN003 / STACKGEN003) reported ex.StackTrace, which
  is null for an exception that never left its throw site and carries neither the
  exception type nor the inner-exception chain. All three call sites -
  SourceGenerator.Guard, StackGeneration.Emit and ModelCompilation.Emit - now
  report ex.ToString() so the detail is identical wherever the failure originates.

Documented, not changed:

- NoRoslynatorAnalzers is misspelled, but common.props is the only reader and it
  uses the same spelling, so the setting does take effect. Renaming only here
  would silently disable it.

- PackageLicenseFile must stay empty. common.props sets it to LICENSE.txt for
  every packable project, and NuGet rejects a package declaring both a license
  expression and a license file (NU5035).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9ed53262-5cfa-429a-820e-bf6146a00d8b
@marcschier marcschier added the ready Ready to merge once CI Passes label Jul 30, 2026
Code review flagged, and a run against a real Roslyn 4.8 compiler confirmed, that
the roslyn4.8 analyzer payload does not work at all.

Directory.Packages.props pins System.Collections.Immutable and
System.Reflection.Metadata centrally with transitive pinning on, so the whole
analyzer closure - the generator, Opc.Ua.SourceGeneration.Core and Opc.Ua.Types -
references 10.0.0.0. Roslyn 4.8 supplies 7.0.0.0. Running csc from
Microsoft.Net.Compilers.Toolset 4.8.0 over the packed roslyn4.8 folder gives:

  warning CS8784: Generator 'ModelSourceGenerator' failed to initialize ...
  MissingMethodException: Method not found:
  'IncrementalValueProvider`1<ImmutableArray`1<!!0>>
   IncrementalValueProviderExtensions.Collect(IncrementalValuesProvider`1<!!0>)'

because the shipped System.Collections.Immutable 10.x binds a second
ImmutableArray<T> identity. Dropping that copy from the payload only changes the
failure to FileNotFoundException for 10.0.0.0. Both surface as a warning, so the
consumer silently gets no generated code.

Fixing it properly means building the entire closure, Opc.Ua.Types included,
against the band's package versions - a change to a core shipping library that
does not belong in this pull request. Note master had the same defect: it was
pinned to Roslyn 4.14 while referencing System.Collections.Immutable 10.0.0.0, so
no 4.x host ever actually worked. Shipping only roslyn5.0 is therefore not a
regression, it is an accurate statement of what has ever worked, and the versioned
folder means an older host now skips the analyzer instead of failing inside it.

- Remove the five *.Roslyn4_8 variant projects, their solution entries, and the
  4.8 half of the pack projects and the MigrationAnalyzer nuspec.
- Never ship Microsoft.CodeAnalysis*, System.Collections.Immutable or
  System.Reflection.Metadata: excluded from both the package payload
  (SourceGeneratorPack.targets) and the in-repo analyzer closure
  (Directory.Build.targets), and asserted in
  validate-source-generator-packages.ps1 so the trap cannot come back.
- Document in RoslynVariants.props and DeveloperGuide.md exactly what adding a
  down-level band requires.

Also from the review: the pack guard tested the flattened assembly list, so it
only fired when *no* variant produced output - with N variants declared it would
silently ship a package missing a band. It now checks each declared variant
contributed assemblies (verified by hiding one variant's output and confirming
the error fires). And the migration skill doc still described the old
analyzers/dotnet/cs layout.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9ed53262-5cfa-429a-820e-bf6146a00d8b
@marcschier marcschier changed the title Remove SourceGenerator.Foundations, multi-target analyzers for VS2022/VS2026, and validate the shipped packages Remove SourceGenerator.Foundations, ship analyzers under a Roslyn-versioned folder, and validate the packages Jul 30, 2026
@codecov

codecov Bot commented Jul 31, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 72.72727% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.91%. Comparing base (8a10139) to head (3804c2f).
⚠️ Report is 5 commits behind head on master.

Files with missing lines Patch % Lines
.../Opc.Ua.SourceGeneration/SourceGeneratorHelpers.cs 31.25% 11 Missing ⚠️
...s/Opc.Ua.SourceGeneration.Stack/StackGeneration.cs 66.66% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #4120      +/-   ##
==========================================
- Coverage   80.23%   79.91%   -0.33%     
==========================================
  Files        1515     1525      +10     
  Lines      209980   210637     +657     
  Branches    36213    36331     +118     
==========================================
- Hits       168479   168324     -155     
- Misses      28867    29657     +790     
- Partials    12634    12656      +22     
Files with missing lines Coverage Δ
...s/Opc.Ua.SourceGeneration.Stack/SourceGenerator.cs 98.11% <ø> (ø)
....Ua.SourceGeneration.Stack/StackSourceGenerator.cs 100.00% <100.00%> (ø)
...ools/Opc.Ua.SourceGeneration/CompilationOptions.cs 85.71% <100.00%> (+2.38%) ⬆️
...ols/Opc.Ua.SourceGeneration/DataTypeCompilation.cs 74.46% <ø> (ø)
tools/Opc.Ua.SourceGeneration/ModelCompilation.cs 77.82% <100.00%> (ø)
...ls/Opc.Ua.SourceGeneration/ModelSourceGenerator.cs 100.00% <100.00%> (ø)
tools/Opc.Ua.SourceGeneration/SourceGenerator.cs 92.26% <ø> (ø)
...pc.Ua.SourceGeneration/SourceGeneratorTelemetry.cs 51.42% <100.00%> (+11.42%) ⬆️
...s/Opc.Ua.SourceGeneration.Stack/StackGeneration.cs 89.33% <66.66%> (ø)
.../Opc.Ua.SourceGeneration/SourceGeneratorHelpers.cs 31.25% <31.25%> (ø)

... and 61 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

marcschier and others added 2 commits July 31, 2026 08:20
…ntime

Two fixes.

Docker build (refserver job on run 30560951546) failed with

  NuGet.targets(198,5): error : Invalid framework identifier ''.
    [/src/samples/ConsoleReferenceServer/ConsoleReferenceServer.csproj]

The Dockerfile restores from a hand-listed set of csproj files plus `COPY *.*`
for the repository root. The generator projects now import shared MSBuild logic
that was in neither list, so their TargetFramework evaluated empty and every
project referencing them failed to restore. Reproduced by replaying the
Dockerfile's copy list into a scratch tree, and confirmed by deleting the three
files again afterwards.

- Copy tools/SourceGeneratorVariant.targets and both *.Sources.targets into the
  restore layer.
- Move tools/RoslynVariants.props to the repository root as roslyn.props.
  Directory.Packages.props imports it, so keeping it under tools/ meant any
  partial-copy context that takes only the root files - which is exactly what the
  sample Dockerfiles do - could not restore. It now sits with common.props,
  targets.props and version.props where the other shared props live.

Second, the roslyn5.0 payload only worked by accident. Microsoft.CodeAnalysis
5.0.0 depends on System.Collections.Immutable 9.0.0, but the central pin put
10.0.0.0 into the whole analyzer closure - the generator,
Opc.Ua.SourceGeneration.Core and Opc.Ua.Types. .NET satisfies a reference from a
higher assembly version but never from a lower one, so the band worked only
because every host that exists today happens to carry 10.x: the .NET 10 SDK
supplies it from the shared framework (it is not in Roslyn/bincore) and Visual
Studio 2026's csc carries 10.0.0.1. A host on the band's declared floor would
have failed the same way the removed roslyn4.8 payload did.

The pin now derives from RoslynRuntimeVersionVS2026 in roslyn.props, so the
closure is 9.0.0.0 throughout and matches what Microsoft.CodeAnalysis 5.0.0
itself references. Verified by scanning assembly references in the packed
payload. Deriving the pin from the band rather than leaving a loose literal is
what stops the closure drifting above the host floor again.

Opc.Ua.Core.Schema.Tests and Opc.Ua.PubSub.Schema.Tests pull
System.Collections.Immutable >= 10.0.5 transitively through JsonSchema.Net; they
are not part of the analyzer closure, so they override the pin locally.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9ed53262-5cfa-429a-820e-bf6146a00d8b
The generator and migration-analyzer packages shipped only
`analyzers/dotnet/roslyn5.0/cs`. The .NET SDK loads the highest band its
compiler supports and silently skips anything above it, so every consumer on
Visual Studio 2022 / .NET 9 SDK got no diagnostics and no generated code, with
only warning CS9057 to show for it.

Ship a second band built against Microsoft.CodeAnalysis 4.14. That version
depends on the same System.Collections.Immutable 9.0.0 as 5.0.0 and its csc
runs on net9.0, so the two bands share $(RoslynRuntimeVersion) and only the
five Roslyn-referencing assemblies need a second build - Opc.Ua.Types and
Opc.Ua.SourceGeneration.Core are compiled once and serve both.

Nothing else in the repository exercises the down-level payload, and every way
it can be wrong is reported as a compiler *warning*, so a broken band would
ship silently. validate-source-generator-packages.ps1 therefore now runs the
packed roslyn4.14 payload through a real Microsoft.Net.Compilers.Toolset 4.14.0
csc and asserts both that no CS9057/CS8784/CS8032/CS8034 is emitted and that
/reportanalyzer lists the generator as having executed - absence of complaints
alone would also pass if the generator were never handed to the compiler. The
migration analyzer, whose payload comes from a hand-written nuspec rather than
SourceGeneratorPack.targets, gets the same treatment plus a band/assembly
manifest check.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9ed53262-5cfa-429a-820e-bf6146a00d8b
@marcschier marcschier changed the title Remove SourceGenerator.Foundations, ship analyzers under a Roslyn-versioned folder, and validate the packages Remove SourceGenerator.Foundations, ship analyzers under Roslyn-versioned folders for VS2022 and VS2026, and validate the packages Jul 31, 2026
@marcschier
marcschier enabled auto-merge (squash) July 31, 2026 11:21
@marcschier
marcschier merged commit 76d33cb into master Jul 31, 2026
124 of 125 checks passed
marcschier added a commit that referenced this pull request Jul 31, 2026
Carries master's #4120 up the stack. The generator tests this change adds still
used the SourceGenerator.Foundations ModelSourceGeneratorHoist wrapper, which
#4120 removed, so they now drive CSharpGeneratorDriver with the generator
directly like the rest of the suite.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8cbb8cd0-f0cb-4ab0-bea2-6202fbf69485
@marcschier
marcschier deleted the marcschier/remove-source-generator-foundations branch August 10, 2026 09:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready Ready to merge once CI Passes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants