2010-04-23 08:58:36 +04:00
/ *
Copyright ( C ) 2010 Jeroen Frijters
This software is provided ' as - is ' , without any express or implied
warranty . In no event will the authors be held liable for any damages
arising from the use of this software .
Permission is granted to anyone to use this software for any purpose ,
including commercial applications , and to alter it and redistribute it
freely , subject to the following restrictions :
1. The origin of this software must not be misrepresented ; you must not
claim that you wrote the original software . If you use this software
in a product , an acknowledgment in the product documentation would be
appreciated but is not required .
2. Altered source versions must be plainly marked as such , and must not be
misrepresented as being the original software .
3. This notice may not be removed or altered from any source distribution .
Jeroen Frijters
jeroen @frijters . net
* /
using System ;
using System.Collections.Generic ;
using System.Text ;
using System.IO ;
using IKVM.Reflection ;
namespace IKVM.Internal
{
sealed class AssemblyResolver
{
private readonly List < string > libpath = new List < string > ( ) ;
2010-10-06 11:39:59 +04:00
private readonly Dictionary < string , string > hintpaths = new Dictionary < string , string > ( ) ;
2010-04-23 08:58:36 +04:00
private Universe universe ;
2010-05-11 13:34:58 +04:00
private Version mscorlibVersion ;
2010-04-23 08:58:36 +04:00
2010-05-18 10:48:40 +04:00
internal enum WarningId
{
HigherVersion = 1 ,
LocationIgnored = 2 ,
InvalidLibDirectoryOption = 3 ,
InvalidLibDirectoryEnvironment = 4 ,
LegacySearchRule = 5 ,
}
internal delegate void WarningEvent ( WarningId warning , string message , string [ ] parameters ) ;
internal event WarningEvent Warning ;
private void EmitWarning ( WarningId warning , string message , params string [ ] parameters )
{
if ( Warning ! = null )
{
Warning ( warning , message , parameters ) ;
}
else
{
Console . Error . WriteLine ( "Warning: " + message , parameters ) ;
}
}
2010-05-11 10:11:03 +04:00
2010-05-12 19:23:17 +04:00
internal void Init ( Universe universe , bool nostdlib , IList < string > references , IList < string > userLibPaths )
2010-04-23 08:58:36 +04:00
{
this . universe = universe ;
// like the C# compiler, the references are loaded from:
// current directory, CLR directory, -lib: option, %LIB% environment
// (note that, unlike the C# compiler, we don't add the CLR directory if -nostdlib has been specified)
libpath . Add ( Environment . CurrentDirectory ) ;
if ( ! nostdlib )
{
libpath . Add ( System . Runtime . InteropServices . RuntimeEnvironment . GetRuntimeDirectory ( ) ) ;
}
foreach ( string str in userLibPaths )
{
2010-05-18 10:48:40 +04:00
AddLibraryPaths ( str , true ) ;
2010-04-23 08:58:36 +04:00
}
2010-05-18 10:48:40 +04:00
AddLibraryPaths ( Environment . GetEnvironmentVariable ( "LIB" ) ? ? "" , false ) ;
2010-04-23 08:58:36 +04:00
if ( nostdlib )
{
2010-05-12 19:23:17 +04:00
mscorlibVersion = LoadMscorlib ( references ) . GetName ( ) . Version ;
2010-04-23 08:58:36 +04:00
}
2010-05-12 19:23:17 +04:00
else
2010-04-23 08:58:36 +04:00
{
2010-05-11 13:34:58 +04:00
mscorlibVersion = universe . Load ( "mscorlib" ) . GetName ( ) . Version ;
2010-04-23 08:58:36 +04:00
}
2010-05-12 19:23:17 +04:00
universe . AssemblyResolve + = new IKVM . Reflection . ResolveEventHandler ( universe_AssemblyResolve ) ;
2010-04-23 08:58:36 +04:00
}
2010-05-11 13:34:58 +04:00
internal Assembly LoadFile ( string path )
{
2010-05-12 12:43:13 +04:00
string ex = null ;
try
2010-05-11 13:34:58 +04:00
{
2010-05-12 12:43:13 +04:00
using ( RawModule module = universe . OpenRawModule ( path ) )
2010-05-11 13:34:58 +04:00
{
2010-05-12 12:43:13 +04:00
if ( mscorlibVersion ! = null )
{
// to avoid problems (i.e. weird exceptions), we don't allow assemblies to load that reference a newer version of mscorlib
foreach ( AssemblyName asmref in module . GetReferencedAssemblies ( ) )
{
if ( asmref . Name = = "mscorlib" & & asmref . Version > mscorlibVersion )
{
Console . Error . WriteLine ( "Error: unable to load assembly '{0}' as it depends on a higher version of mscorlib than the one currently loaded" , path ) ;
Environment . Exit ( 1 ) ;
}
}
}
Assembly asm = universe . LoadAssembly ( module ) ;
2010-05-18 10:55:15 +04:00
if ( asm . Location ! = module . Location & & CanonicalizePath ( asm . Location ) ! = CanonicalizePath ( module . Location ) )
2010-05-12 12:43:13 +04:00
{
2010-05-18 10:48:40 +04:00
EmitWarning ( WarningId . LocationIgnored , "assembly \"{0}\" is ignored as previously loaded assembly \"{1}\" has the same identity \"{2}\"" , path , asm . Location , asm . FullName ) ;
2010-05-12 12:43:13 +04:00
}
return asm ;
2010-05-11 13:34:58 +04:00
}
}
2010-05-12 12:43:13 +04:00
catch ( IOException x )
{
ex = x . Message ;
}
catch ( UnauthorizedAccessException x )
{
ex = x . Message ;
}
catch ( IKVM . Reflection . BadImageFormatException x )
{
ex = x . Message ;
}
Console . Error . WriteLine ( "Error: unable to load assembly '{0}'" + Environment . NewLine + " ({1})" , path , ex ) ;
Environment . Exit ( 1 ) ;
return null ;
2010-05-11 13:34:58 +04:00
}
2010-05-18 10:55:15 +04:00
private static string CanonicalizePath ( string path )
{
try
{
System . IO . FileInfo fi = new System . IO . FileInfo ( path ) ;
if ( fi . DirectoryName = = null )
{
return path . Length > 1 & & path [ 1 ] = = ':' ? path . ToUpper ( ) : path ;
}
string dir = CanonicalizePath ( fi . DirectoryName ) ;
string name = fi . Name ;
try
{
string [ ] arr = System . IO . Directory . GetFileSystemEntries ( dir , name ) ;
if ( arr . Length = = 1 )
{
name = arr [ 0 ] ;
}
}
catch ( System . UnauthorizedAccessException )
{
}
catch ( System . IO . IOException )
{
}
return System . IO . Path . Combine ( dir , name ) ;
}
catch ( System . UnauthorizedAccessException )
{
}
catch ( System . IO . IOException )
{
}
catch ( System . Security . SecurityException )
{
}
catch ( System . NotSupportedException )
{
}
return path ;
}
2010-04-23 08:58:36 +04:00
internal Assembly LoadWithPartialName ( string name )
{
foreach ( string path in FindAssemblyPath ( name + ".dll" ) )
{
2010-05-11 13:34:58 +04:00
return LoadFile ( path ) ;
2010-04-23 08:58:36 +04:00
}
return null ;
}
internal int ResolveReference ( Dictionary < string , Assembly > cache , ref Assembly [ ] references , string reference )
{
string [ ] files = new string [ 0 ] ;
try
{
string path = Path . GetDirectoryName ( reference ) ;
files = Directory . GetFiles ( path = = "" ? "." : path , Path . GetFileName ( reference ) ) ;
}
catch ( ArgumentException )
{
}
catch ( IOException )
{
}
if ( files . Length = = 0 )
{
Assembly asm = null ;
cache . TryGetValue ( reference , out asm ) ;
2010-05-12 12:43:13 +04:00
if ( asm = = null )
2010-04-23 08:58:36 +04:00
{
2010-05-12 12:43:13 +04:00
foreach ( string found in FindAssemblyPath ( reference ) )
2010-04-23 08:58:36 +04:00
{
2010-05-12 12:43:13 +04:00
asm = LoadFile ( found ) ;
cache . Add ( reference , asm ) ;
break ;
2010-04-23 08:58:36 +04:00
}
}
if ( asm = = null )
{
Console . Error . WriteLine ( "Error: reference not found: {0}" , reference ) ;
return 1 ;
}
ArrayAppend ( ref references , asm ) ;
}
else
{
foreach ( string file in files )
{
2010-05-12 12:43:13 +04:00
Assembly asm ;
if ( ! cache . TryGetValue ( file , out asm ) )
2010-04-23 08:58:36 +04:00
{
2010-05-12 12:43:13 +04:00
asm = LoadFile ( file ) ;
2010-04-23 08:58:36 +04:00
}
2010-05-12 12:43:13 +04:00
ArrayAppend ( ref references , asm ) ;
2010-04-23 08:58:36 +04:00
}
}
return 0 ;
}
private static void ArrayAppend < T > ( ref T [ ] array , T element )
{
if ( array = = null )
{
array = new T [ ] { element } ;
}
else
{
T [ ] temp = new T [ array . Length + 1 ] ;
Array . Copy ( array , 0 , temp , 0 , array . Length ) ;
temp [ temp . Length - 1 ] = element ;
array = temp ;
}
}
private Assembly universe_AssemblyResolve ( object sender , IKVM . Reflection . ResolveEventArgs args )
{
AssemblyName name = new AssemblyName ( args . Name ) ;
2010-05-11 10:11:03 +04:00
AssemblyName previousMatch = null ;
int previousMatchLevel = 0 ;
2010-05-10 12:22:03 +04:00
foreach ( Assembly asm in universe . GetAssemblies ( ) )
{
2010-05-11 10:11:03 +04:00
if ( Match ( asm . GetName ( ) , name , ref previousMatch , ref previousMatchLevel ) )
2010-05-10 12:22:03 +04:00
{
return asm ;
}
}
2010-04-23 08:58:36 +04:00
foreach ( string file in FindAssemblyPath ( name . Name + ".dll" ) )
{
2010-05-11 19:22:15 +04:00
if ( Match ( AssemblyName . GetAssemblyName ( file ) , name , ref previousMatch , ref previousMatchLevel ) )
2010-04-23 08:58:36 +04:00
{
2010-05-11 13:34:58 +04:00
return LoadFile ( file ) ;
2010-04-23 08:58:36 +04:00
}
}
if ( args . RequestingAssembly ! = null )
{
string path = Path . Combine ( Path . GetDirectoryName ( args . RequestingAssembly . Location ) , name . Name + ".dll" ) ;
2010-05-11 10:11:03 +04:00
if ( File . Exists ( path ) & & Match ( AssemblyName . GetAssemblyName ( path ) , name , ref previousMatch , ref previousMatchLevel ) )
2010-04-23 08:58:36 +04:00
{
2010-05-11 13:34:58 +04:00
return LoadFile ( path ) ;
2010-04-23 08:58:36 +04:00
}
}
2010-10-06 11:39:59 +04:00
string hintpath ;
if ( hintpaths . TryGetValue ( name . FullName , out hintpath ) )
{
string path = Path . Combine ( hintpath , name . Name + ".dll" ) ;
if ( File . Exists ( path ) & & Match ( AssemblyName . GetAssemblyName ( path ) , name , ref previousMatch , ref previousMatchLevel ) )
{
return LoadFile ( path ) ;
}
}
2010-05-11 10:11:03 +04:00
if ( previousMatch ! = null )
{
if ( previousMatchLevel = = 2 )
{
2010-05-18 10:48:40 +04:00
EmitWarning ( WarningId . HigherVersion , "assuming assembly reference \"{0}\" matches \"{1}\", you may need to supply runtime policy" , previousMatch . FullName , name . FullName ) ;
2010-05-11 13:34:58 +04:00
return LoadFile ( new Uri ( previousMatch . CodeBase ) . LocalPath ) ;
2010-05-11 10:11:03 +04:00
}
else if ( args . RequestingAssembly ! = null )
{
Console . Error . WriteLine ( "Error: Assembly '{0}' uses '{1}' which has a higher version than referenced assembly '{2}'" , args . RequestingAssembly . FullName , name . FullName , previousMatch . FullName ) ;
}
else
{
Console . Error . WriteLine ( "Error: Assembly '{0}' was requested which is a higher version than referenced assembly '{1}'" , name . FullName , previousMatch . FullName ) ;
}
}
else
2010-04-23 08:58:36 +04:00
{
2010-05-11 10:11:03 +04:00
Console . Error . WriteLine ( "Error: unable to find assembly '{0}'" , args . Name ) ;
if ( args . RequestingAssembly ! = null )
{
Console . Error . WriteLine ( " (a dependency of '{0}')" , args . RequestingAssembly . FullName ) ;
}
2010-04-23 08:58:36 +04:00
}
Environment . Exit ( 1 ) ;
return null ;
}
2010-05-11 19:22:15 +04:00
private bool Match ( AssemblyName assemblyDef , AssemblyName assemblyRef , ref AssemblyName bestMatch , ref int bestMatchLevel )
2010-04-23 08:58:36 +04:00
{
2010-05-11 10:11:03 +04:00
// Match levels:
// 0 = no match
// 1 = lower version match (i.e. not a suitable match, but used in error reporting: something was found but the version was too low)
// 2 = higher version potential match (i.e. we can use this version, but if it is available the exact match will be preferred)
/ /
2010-05-11 19:22:15 +04:00
// If we find a perfect match, bestMatch is not changed but we return true to signal that the search can end right now.
AssemblyComparisonResult result ;
universe . CompareAssemblyIdentity ( assemblyRef . FullName , false , assemblyDef . FullName , true , out result ) ;
switch ( result )
2010-04-23 08:58:36 +04:00
{
2010-05-11 19:22:15 +04:00
case AssemblyComparisonResult . EquivalentFullMatch :
case AssemblyComparisonResult . EquivalentPartialMatch :
case AssemblyComparisonResult . EquivalentFXUnified :
case AssemblyComparisonResult . EquivalentPartialFXUnified :
case AssemblyComparisonResult . EquivalentPartialWeakNamed :
case AssemblyComparisonResult . EquivalentWeakNamed :
return true ;
case AssemblyComparisonResult . NonEquivalentPartialVersion :
case AssemblyComparisonResult . NonEquivalentVersion :
2010-05-11 10:11:03 +04:00
if ( bestMatchLevel < 1 )
{
bestMatchLevel = 1 ;
bestMatch = assemblyDef ;
}
return false ;
2010-05-11 19:22:15 +04:00
case AssemblyComparisonResult . EquivalentUnified :
case AssemblyComparisonResult . EquivalentPartialUnified :
2010-05-11 10:11:03 +04:00
if ( bestMatchLevel < 2 )
{
bestMatchLevel = 2 ;
bestMatch = assemblyDef ;
}
return false ;
2010-05-11 19:22:15 +04:00
case AssemblyComparisonResult . NonEquivalent :
case AssemblyComparisonResult . Unknown :
2010-04-23 08:58:36 +04:00
return false ;
2010-05-11 19:22:15 +04:00
default :
throw new NotImplementedException ( ) ;
2010-04-23 08:58:36 +04:00
}
}
2010-05-18 10:48:40 +04:00
private void AddLibraryPaths ( string str , bool option )
2010-04-23 08:58:36 +04:00
{
foreach ( string dir in str . Split ( Path . PathSeparator ) )
{
if ( Directory . Exists ( dir ) )
{
libpath . Add ( dir ) ;
}
else if ( dir ! = "" )
{
2010-05-18 10:48:40 +04:00
if ( option )
{
EmitWarning ( WarningId . InvalidLibDirectoryOption , "directory \"{0}\" specified in -lib option is not valid" , dir ) ;
}
else
{
EmitWarning ( WarningId . InvalidLibDirectoryEnvironment , "directory \"{0}\" specified in LIB environment is not valid" , dir ) ;
}
2010-04-23 08:58:36 +04:00
}
}
}
2010-05-12 19:23:17 +04:00
private Assembly LoadMscorlib ( IList < string > references )
2010-04-23 08:58:36 +04:00
{
2010-04-23 11:53:21 +04:00
if ( references ! = null )
2010-04-23 08:58:36 +04:00
{
2010-04-23 11:53:21 +04:00
foreach ( string r in references )
2010-04-23 08:58:36 +04:00
{
2010-04-23 11:53:21 +04:00
try
{
2010-05-10 11:18:05 +04:00
if ( AssemblyName . GetAssemblyName ( r ) . Name = = "mscorlib" )
2010-04-23 11:53:21 +04:00
{
2010-05-12 19:23:17 +04:00
return LoadFile ( r ) ;
2010-04-23 11:53:21 +04:00
}
}
catch
2010-04-23 08:58:36 +04:00
{
}
}
}
foreach ( string mscorlib in FindAssemblyPath ( "mscorlib.dll" ) )
{
2010-05-12 19:23:17 +04:00
return LoadFile ( mscorlib ) ;
2010-04-23 08:58:36 +04:00
}
Console . Error . WriteLine ( "Error: unable to find mscorlib.dll" ) ;
2010-05-12 19:23:17 +04:00
Environment . Exit ( 1 ) ;
return null ;
2010-04-23 08:58:36 +04:00
}
private IEnumerable < string > FindAssemblyPath ( string file )
{
if ( Path . IsPathRooted ( file ) )
{
if ( File . Exists ( file ) )
{
yield return file ;
}
}
else
{
foreach ( string dir in libpath )
{
string path = Path . Combine ( dir , file ) ;
if ( File . Exists ( path ) )
{
yield return path ;
}
// for legacy compat, we try again after appending .dll
path = Path . Combine ( dir , file + ".dll" ) ;
if ( File . Exists ( path ) )
{
2010-05-18 10:48:40 +04:00
EmitWarning ( WarningId . LegacySearchRule , "found assembly \"{0}\" using legacy search rule, please append '.dll' to the reference" , file ) ;
2010-04-23 08:58:36 +04:00
yield return path ;
}
}
}
}
2010-10-06 11:39:59 +04:00
internal void AddHintPath ( string assemblyName , string path )
{
hintpaths . Add ( assemblyName , path ) ;
}
2010-04-23 08:58:36 +04:00
}
}