From 6b04630d150bb7da365632a113be2aa74c9aaf87 Mon Sep 17 00:00:00 2001 From: Daniel Woodward <16451892+DrBarnabus@users.noreply.github.com> Date: Sun, 22 Feb 2026 16:55:31 +0000 Subject: [PATCH] build: Fix parallel build race on Analyzers.Common deps file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous fix (#37) added GlobalPropertiesToRemove="TargetFramework" to all direct references to Analyzers.Common, but transient CI failures persisted. Investigation revealed two root causes: 1. Missing fix on Analyzers.UnitTests -> Analyzers reference: The 3-TFM test project (net10.0/net9.0/net8.0) passed its own TargetFramework to the multi-targeted Analyzers project without SetTargetFramework, creating 3 parallel build configurations that all converged on Analyzers.Common simultaneously. 2. Inconsistent ProjectReference strategies across consuming projects: FunctionalTests used SetTargetFramework="TargetFramework=netstandard2.0" on its Analyzers.Common reference while all other projects used GlobalPropertiesToRemove="TargetFramework". These produce different MSBuild global property sets ({TargetFramework=netstandard2.0} vs {}) for the same single-targeted project, creating two distinct build configurations both writing to the same output directory — the actual source of the race condition. The correct strategy depends on whether the referenced project is single-targeted or multi-targeted: - Single-targeted (Analyzers.Common): Use GlobalPropertiesToRemove to strip TargetFramework. This produces a config matching the solution's natural build, so MSBuild deduplicates correctly. - Multi-targeted (Analyzers, SourceGeneration): Use SetTargetFramework to pin to netstandard2.0. This matches one of the inner builds dispatched by the solution's outer multi-target build. Additionally, --graph is added to the CI build command as defense-in-depth. Graph builds evaluate the full project dependency graph upfront and ensure each project+config is built exactly once in topological order. --- .github/workflows/ci.yml | 2 +- .../CompositeKey.Analyzers.UnitTests.csproj | 2 +- .../CompositeKey.SourceGeneration.FunctionalTests.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c45dc90..b50969c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -74,7 +74,7 @@ jobs: - name: Restore run: dotnet restore --locked-mode - name: Build - run: dotnet build -c Release --no-restore -p:Version=${{ steps.gitversion.outputs.semVer }} -p:PackageVersion=${{ steps.gitversion.outputs.semVer }} + run: dotnet build -c Release --no-restore --graph -p:Version=${{ steps.gitversion.outputs.semVer }} -p:PackageVersion=${{ steps.gitversion.outputs.semVer }} - name: Unit Test run: | dotnet test src/CompositeKey.SourceGeneration.UnitTests -c Release --no-restore --no-build --logger:"junit;LogFileName=sourcegeneration.{framework}.results.xml" --collect:"XPlat Code Coverage" --results-directory unit-test-results -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover diff --git a/src/CompositeKey.Analyzers.UnitTests/CompositeKey.Analyzers.UnitTests.csproj b/src/CompositeKey.Analyzers.UnitTests/CompositeKey.Analyzers.UnitTests.csproj index d2a3a17..b023f94 100644 --- a/src/CompositeKey.Analyzers.UnitTests/CompositeKey.Analyzers.UnitTests.csproj +++ b/src/CompositeKey.Analyzers.UnitTests/CompositeKey.Analyzers.UnitTests.csproj @@ -35,7 +35,7 @@ - + diff --git a/src/CompositeKey.SourceGeneration.FunctionalTests/CompositeKey.SourceGeneration.FunctionalTests.csproj b/src/CompositeKey.SourceGeneration.FunctionalTests/CompositeKey.SourceGeneration.FunctionalTests.csproj index 5ce39d8..3a67a30 100644 --- a/src/CompositeKey.SourceGeneration.FunctionalTests/CompositeKey.SourceGeneration.FunctionalTests.csproj +++ b/src/CompositeKey.SourceGeneration.FunctionalTests/CompositeKey.SourceGeneration.FunctionalTests.csproj @@ -33,7 +33,7 @@ - +