From 40cb4098f1bd044d4934a0aef1b78198bd17d3ad Mon Sep 17 00:00:00 2001 From: huulinh99 Date: Wed, 10 Dec 2025 14:05:54 +0700 Subject: [PATCH] Change version check from != to < to allow loading newer assembly versions when older versions are requested. --- .../BackEnd/AssemblyLoadContextTestTasks.cs | 58 +++++++++++++++++++ .../BackEnd/TaskBuilder_Tests.cs | 27 +++++++++ src/Shared/MSBuildLoadContext.cs | 2 +- 3 files changed, 86 insertions(+), 1 deletion(-) diff --git a/src/Build.UnitTests/BackEnd/AssemblyLoadContextTestTasks.cs b/src/Build.UnitTests/BackEnd/AssemblyLoadContextTestTasks.cs index b11055c16c5..be9dcb0b600 100644 --- a/src/Build.UnitTests/BackEnd/AssemblyLoadContextTestTasks.cs +++ b/src/Build.UnitTests/BackEnd/AssemblyLoadContextTestTasks.cs @@ -3,11 +3,69 @@ using Microsoft.Build.Framework; using Microsoft.Build.Utilities; +using System; +using System.Reflection; #nullable disable namespace AssemblyLoadContextTest { + /// + /// Task that validates assembly version roll-forward behavior. + /// Tests that MSBuildLoadContext accepts newer assembly versions when older versions are requested. + /// + public class ValidateAssemblyVersionRollForward : Task + { + /// + /// The name of the assembly to check (e.g., "System.Collections.Immutable") + /// + [Required] + public string AssemblyName { get; set; } + + /// + /// The minimum expected version (e.g., "1.0.0.0") + /// + [Required] + public string MinimumVersion { get; set; } + + public override bool Execute() + { + try + { + // Try to load the assembly by name with minimum version + var minimumVersion = Version.Parse(MinimumVersion); + var assemblyName = new AssemblyName(AssemblyName) + { + Version = minimumVersion + }; + + // This will trigger MSBuildLoadContext.Load which should accept newer versions + var assembly = Assembly.Load(assemblyName); + var loadedVersion = assembly.GetName().Version; + + Log.LogMessage(MessageImportance.High, + $"Requested {AssemblyName} version {minimumVersion}, loaded version {loadedVersion}"); + + // Verify that we got a version >= minimum + if (loadedVersion < minimumVersion) + { + Log.LogError( + $"Assembly version roll-forward failed: requested {minimumVersion}, but loaded {loadedVersion} which is older"); + return false; + } + + Log.LogMessage(MessageImportance.High, + $"Assembly version roll-forward succeeded: loaded version {loadedVersion} >= requested {minimumVersion}"); + return true; + } + catch (Exception ex) + { + Log.LogErrorFromException(ex, showStackTrace: true); + return false; + } + } + } + public class RegisterObject : Task { internal const string CacheKey = "RegressionForMSBuild#5080"; diff --git a/src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs b/src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs index 9bec89727fe..7d80283306d 100644 --- a/src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs @@ -662,6 +662,33 @@ public void SameAssemblyFromDifferentRelativePathsSharesAssemblyLoadContext() logger.AssertLogDoesntContain("MSB4018"); } +#if FEATURE_ASSEMBLYLOADCONTEXT + /// + /// Regression test for https://github.com/dotnet/msbuild/issues/12370 + /// Verifies that MSBuildLoadContext accepts newer assembly versions when older versions are requested (version roll-forward). + /// + [Fact] + public void MSBuildLoadContext_AcceptsNewerAssemblyVersions() + { + string realTaskPath = Assembly.GetExecutingAssembly().Location; + + // Use System.Collections.Immutable as test assembly - it's available in modern .NET runtime + // Request an older version (1.0.0.0) which should roll forward to whatever version is available + string projectContents = @" + + + + + +"; + + MockLogger logger = ObjectModelHelpers.BuildProjectExpectSuccess(projectContents, _testOutput); + + // Verify that the task logged success message + logger.AssertLogContains("Assembly version roll-forward succeeded"); + } +#endif + #if FEATURE_CODETASKFACTORY /// diff --git a/src/Shared/MSBuildLoadContext.cs b/src/Shared/MSBuildLoadContext.cs index 6e322309312..3e797529682 100644 --- a/src/Shared/MSBuildLoadContext.cs +++ b/src/Shared/MSBuildLoadContext.cs @@ -83,7 +83,7 @@ public MSBuildLoadContext(string assemblyPath) } AssemblyName candidateAssemblyName = AssemblyLoadContext.GetAssemblyName(candidatePath); - if (candidateAssemblyName.Version != assemblyName.Version) + if (candidateAssemblyName.Version < assemblyName.Version) { continue; }