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 @@
-
+