Repository navigation
build: take Purview.Build 0.5.3 - #40
Merged
Merged
Conversation
…ions ResolveReferences created a fresh MetadataReference per run for every assembly named by AdditionalAssemblyTypes, so Roslyn could not reuse PEAssemblySymbols and each test compilation built its own symbol tables. References are now created once per path, process-wide, and an assembly already in the trusted-platform set is not added a second time. TRUSTED_PLATFORM_ASSEMBLIES is deduplicated at the source: the host lists a path once per deps.json library that resolves to it, so the "reference the generator project twice" pattern put the same file in the list twice. Additions are deduplicated because Solution.AddMetadataReferences rejects duplicate items once the instances are shared. Measured on zodsharp SourceGenerators.UnitTests (1,289 tests, four TFM executables concurrently under dotnet test): 15,340 MB -> 3,450 MB peak, 35.6s -> 28.5s wall clock. An isolated 100-compilation probe isolates the mechanism: 156 MB with shared references against 8,146 MB with per-compilation references.
DriverRunResult is IDisposable because a run that compiles to an assembly creates a collectible AssemblyLoadContext and a MetadataLoadContext. Nothing enforced disposal, and consumers accumulated undisposed results without noticing. PSGFR42 flags a disposable result from the Testing namespace that is produced and then never disposed: no using declaration, not returned to a caller, not disposed explicitly. The indirect case is covered too, where a helper creates a result, projects a value out of it and returns only the projection. Discarded results are flagged as well. Severity is keyed syntactically on whether the run compiles to an assembly (a Compile options member, or CompileToAssembly = true): Warning when it does, because that is when the leak holds an assembly and its load contexts, and Info otherwise. DisposeTestResultCodeFixProvider turns the declaration into a using declaration, with Fix-all support. The framework's own CompileToAssembly tests are fixed accordingly, and the Testing docs and skills record the rule along with the harness reference-sharing behaviour and the parallelism-ceiling guidance.
The cache was a ConditionalWeakTable<IAssemblySymbol, ConcurrentDictionary<ITypeSymbol, …>> keyed with SymbolEqualityComparer.Default. That was wrong twice over. A TypeIdentity records its type arguments as TypeReference values, and those carry the nullable reference annotation — but SymbolEqualityComparer.Default ignores it. List<string?> and List<string> therefore shared one entry, and whichever was constructed first won for every later caller. That is the cause of a generator emitting RequiredRule<string?> for a non-nullable member once an earlier compilation in the same process had asked for the nullable form. A constructed generic also reports its *definition's* assembly as ContainingAssembly, so the entry hung off the long-lived corlib assembly symbol while the key strongly referenced source symbols, pinning whole compilations. A probe of 100 compilations that each take a TypeIdentity of List<SourceType> kept 100/100 alive after a forced collection; it now keeps 1/100, the same as taking no identity at all. Both defects only became observable process-wide once the Testing harness started sharing MetadataReference instances, because that made the corlib assembly symbol — and so the cache entry — live for the whole run. Keying weakly on the symbol fixes both and is faster: TypeIdentityBenchmarks moves from 13.7-15.7 ns to 8.0-8.9 ns, still zero-allocation.
A scoped query - query.In(node), or the Query carried by any CodeQueryResult - has exactly one root no matter how many trees it carries, but every search enumerated Trees and resolved RootOf(tree), which returns that one root each time. Count<T>() and GetAll<T>() therefore multiplied every match by the number of trees, and the Get/Has/TryGet family walked the same subtree once per tree before concluding a node was absent. It was invisible in the existing tests because they all query single-tree compilations; it bites on result.Output(), where the compilation carries the user source plus every generated tree. Searches now enumerate Roots(), which yields the scoping node alone when the query is nested.
SourceGeneratorTestRunnerBenchmarks left every DriverRunResult undisposed, so the CompileToAssembly variants accumulated one collectible AssemblyLoadContext per iteration and measured the leak alongside the run. Flagged by PSGFR42.
…p entries ordered RunIncremental created a logging session id and baked it into the shared driver, but never called ConfigureLogging. No sink was ever registered for that id, so CreateLogger returned null and every framework log entry written during RunIncrementalAsync/GenerateIncrementalAsync was silently dropped. The sink is now registered for the whole sequence - the driver is shared, so it has to outlive the individual runs - and drained between runs, so each IncrementalCacheRun carries only what that run wrote through a new LogEntries property. A rerun whose output step is cached logs nothing, which makes logging a second signal alongside the step reasons. ConfigureLogging also registers with a self-contained generator's embedded SourceGenLogging copy, so merged analyzers are covered on this path too. RunAsync collected entries into a ConcurrentBag, whose enumeration order is unspecified, so DriverRunResult.LogEntries came back in an arbitrary order. It is a ConcurrentQueue now and both paths report entries in the order the generator wrote them. LogEntries is an init-only property rather than a positional record parameter, so the IncrementalCacheRun constructor stays source- and binary-compatible.
Add Purview.Build 0.5.2 to .config/dotnet-tools.json and replace the hand-rolled dotnet tool install --tool-path .tools/purview-build in the Justfile with dotnet tool restore, running the pipeline as dotnet purview-build. The version is now pinned in the tool manifest alongside CSharpier, so it is restored by the same command as every other tool and needs no bespoke install step, no --add-source feed argument and no platform-specific executable suffix. The dead pipeline_feed variable is removed, and the docs describing the old .tools/purview-build path are updated to match.
Purview.Build 0.5.3 defaults RequiredCompilerFlags to empty. Earlier versions forced the optimization=release check on and could not be configured off, because binding an empty JSON array contributes no configuration keys and so never overwrote the tool's non-empty code default. These packages have therefore always been validated against that flag. Declaring it keeps that behaviour rather than silently losing the check on the move to 0.5.3. Verified with just pipeline-pack-validate: all packages still valid.
Picks up the fix that makes RequiredCompilerFlags configurable: earlier versions forced the compiler-flag check on because binding an empty JSON array contributes no configuration keys, so the tool's non-empty code default could never be overridden. This repository declares "RequiredCompilerFlags": ["optimization=release"] explicitly, so the check it has always run under is preserved. Verified with just pipeline-pack-validate against 0.5.3.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.