22using System . IO ;
33using System . Linq ;
44using System . Reflection ;
5+ #if ! NET472
6+ using System . Runtime . Loader ;
7+ #endif
58using Microsoft . Build . Framework ;
69using Microsoft . Build . Utilities ;
710using JD . MSBuild . Fluent ;
@@ -45,17 +48,50 @@ public class GenerateMSBuildAssets : Task
4548 [ Output ]
4649 public ITaskItem [ ] GeneratedFiles { get ; set ; } = Array . Empty < ITaskItem > ( ) ;
4750
51+ #if ! NET472
52+ private class DefinitionFactoryContext : AssemblyLoadContext
53+ {
54+ private readonly AssemblyDependencyResolver _resolver ;
55+ private readonly string _taskDirectory ;
56+
57+ public DefinitionFactoryContext ( string assemblyPath , string taskDirectory )
58+ : base ( isCollectible : true )
59+ {
60+ _resolver = new AssemblyDependencyResolver ( assemblyPath ) ;
61+ _taskDirectory = taskDirectory ;
62+ }
63+
64+ protected override Assembly ? Load ( AssemblyName assemblyName )
65+ {
66+ // CRITICAL: Load JD.MSBuild.Fluent from the task directory to ensure type identity
67+ if ( assemblyName . Name == "JD.MSBuild.Fluent" )
68+ {
69+ var fluentPath = Path . Combine ( _taskDirectory , "JD.MSBuild.Fluent.dll" ) ;
70+ if ( File . Exists ( fluentPath ) )
71+ return LoadFromAssemblyPath ( fluentPath ) ;
72+ }
73+
74+ // Use the dependency resolver for other assemblies
75+ var assemblyPath = _resolver . ResolveAssemblyToPath ( assemblyName ) ;
76+ if ( assemblyPath != null )
77+ return LoadFromAssemblyPath ( assemblyPath ) ;
78+
79+ // Fall back to default context for framework assemblies
80+ return null ;
81+ }
82+ }
83+ #else
4884 static GenerateMSBuildAssets ( )
4985 {
50- // Register assembly resolver to find JD.MSBuild.Fluent.dll in the same directory as the task DLL
86+ // For .NET Framework, register assembly resolver
5187 AppDomain . CurrentDomain . AssemblyResolve += OnAssemblyResolve ;
5288 }
5389
5490 private static Assembly ? OnAssemblyResolve ( object ? sender , ResolveEventArgs args )
5591 {
5692 var assemblyName = new AssemblyName ( args . Name ) ;
5793
58- // Look in the same directory as the task DLL for ANY assembly
94+ // Look in the same directory as the task DLL for assemblies
5995 var taskAssemblyLocation = typeof ( GenerateMSBuildAssets ) . Assembly . Location ;
6096 var taskDirectory = Path . GetDirectoryName ( taskAssemblyLocation ) ;
6197 if ( string . IsNullOrEmpty ( taskDirectory ) )
@@ -64,6 +100,7 @@ static GenerateMSBuildAssets()
64100 var assemblyPath = Path . Combine ( taskDirectory , assemblyName . Name + ".dll" ) ;
65101 return File . Exists ( assemblyPath ) ? Assembly . LoadFrom ( assemblyPath ) : null ;
66102 }
103+ #endif
67104
68105 public override bool Execute ( )
69106 {
@@ -111,25 +148,42 @@ public override bool Execute()
111148
112149 private PackageDefinition ? LoadDefinitionFromFactory ( )
113150 {
151+ #if ! NET472
152+ DefinitionFactoryContext ? context = null ;
153+ #endif
154+
114155 try
115156 {
116- // CRITICAL: Pre-load JD.MSBuild.Fluent.dll from the task directory FIRST
117- // This ensures the user assembly uses OUR copy, not a potentially different one from their bin folder
157+ // Get task directory for loading JD.MSBuild.Fluent
118158 var taskAssemblyLocation = typeof ( GenerateMSBuildAssets ) . Assembly . Location ;
119159 var taskDirectory = Path . GetDirectoryName ( taskAssemblyLocation ) ;
120- if ( ! string . IsNullOrEmpty ( taskDirectory ) )
160+ if ( string . IsNullOrEmpty ( taskDirectory ) )
121161 {
122- var fluentAssemblyPath = Path . Combine ( taskDirectory , "JD.MSBuild.Fluent.dll" ) ;
123- if ( File . Exists ( fluentAssemblyPath ) )
124- {
125- // Force load this assembly into the default context BEFORE loading user assembly
126- var fluentAsm = Assembly . LoadFrom ( fluentAssemblyPath ) ;
127- Log . LogMessage ( MessageImportance . Low , $ "Pre-loaded JD.MSBuild.Fluent from: { fluentAsm . Location } ") ;
128- }
162+ Log . LogError ( "Could not determine task assembly directory" ) ;
163+ return null ;
129164 }
165+
166+ #if ! NET472
167+ // Create isolated load context that forces JD.MSBuild.Fluent to load from task directory
168+ context = new DefinitionFactoryContext ( AssemblyFile , taskDirectory ) ;
130169
131- // Now load user assembly - it should resolve JD.MSBuild.Fluent to the one we just loaded
170+ // Load user assembly in the isolated context
171+ var assembly = context . LoadFromAssemblyPath ( AssemblyFile ) ;
172+ #else
173+ // For .NET Framework, pre-load JD.MSBuild.Fluent from task directory
174+ var fluentAssemblyPath = Path . Combine ( taskDirectory , "JD.MSBuild.Fluent.dll" ) ;
175+ if ( File . Exists ( fluentAssemblyPath ) )
176+ {
177+ var fluentAsm = Assembly . LoadFrom ( fluentAssemblyPath ) ;
178+ Log . LogMessage ( MessageImportance . Low , $ "Pre-loaded JD.MSBuild.Fluent from: { fluentAsm . Location } ") ;
179+ }
180+
181+ // Load user assembly
132182 var assembly = Assembly . LoadFrom ( AssemblyFile ) ;
183+ #endif
184+
185+ Log . LogMessage ( MessageImportance . Low ,
186+ $ "Loaded user assembly: { assembly . FullName } from { assembly . Location } ") ;
133187
134188 // Find type
135189 var type = assembly . GetType ( FactoryType ) ;
@@ -170,18 +224,18 @@ public override bool Execute()
170224 return null ;
171225 }
172226
173- // The result is a PackageDefinition from the user's assembly context
174- // We need to work with it via reflection since it's a different type identity
227+ // Try to cast to PackageDefinition
175228 var packageDef = result as PackageDefinition ;
176229 if ( packageDef == null )
177230 {
178- // Log detailed type information for debugging
231+ // Type identity mismatch - keep diagnostics for debugging
179232 var resultType = result . GetType ( ) ;
180233 var expectedType = typeof ( PackageDefinition ) ;
181- Log . LogError ( $ "Factory method returned type { resultType . FullName } from assembly { resultType . Assembly . FullName } ") ;
182- Log . LogError ( $ "Expected type { expectedType . FullName } from assembly { expectedType . Assembly . FullName } ") ;
183- Log . LogError ( $ "Types equal: { resultType == expectedType } ") ;
184- Log . LogError ( $ "Assemblies equal: { resultType . Assembly == expectedType . Assembly } ") ;
234+ Log . LogError ( $ "Type identity mismatch!") ;
235+ Log . LogError ( $ " Returned: { resultType . FullName } from { resultType . Assembly . Location } ") ;
236+ Log . LogError ( $ " Expected: { expectedType . FullName } from { expectedType . Assembly . Location } ") ;
237+ Log . LogError ( $ " Types equal: { resultType == expectedType } ") ;
238+ Log . LogError ( $ " Assemblies equal: { resultType . Assembly . FullName == expectedType . Assembly . FullName } ") ;
185239 return null ;
186240 }
187241
@@ -202,8 +256,15 @@ public override bool Execute()
202256 }
203257 catch ( Exception ex )
204258 {
205- Log . LogErrorFromException ( ex ) ;
259+ Log . LogErrorFromException ( ex , showStackTrace : true ) ;
206260 return null ;
207261 }
262+ finally
263+ {
264+ #if ! NET472
265+ // Don't unload the context yet - we still need the PackageDefinition object
266+ // MSBuild will clean up after the task completes
267+ #endif
268+ }
208269 }
209270}
0 commit comments