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 > ( ) ;
private Universe universe ;
2010-05-11 13:34:58 +04:00
private Version mscorlibVersion ;
2010-04-23 08:58:36 +04:00
2010-05-11 10:11:03 +04:00
internal delegate void HigherVersionEvent ( AssemblyName assemblyDef , AssemblyName assemblyRef ) ;
internal event HigherVersionEvent HigherVersion ;
2010-04-23 08:58:36 +04:00
internal int Init ( Universe universe , bool nostdlib , IList < string > references , IList < string > userLibPaths )
{
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 )
{
AddLibraryPaths ( str , "-lib option" ) ;
}
AddLibraryPaths ( Environment . GetEnvironmentVariable ( "LIB" ) ? ? "" , "LIB environment" ) ;
int rc = 0 ;
if ( nostdlib )
{
rc = LoadMscorlib ( references ) ;
}
if ( rc = = 0 )
{
2010-05-11 13:34:58 +04:00
mscorlibVersion = universe . Load ( "mscorlib" ) . GetName ( ) . Version ;
2010-04-23 08:58:36 +04:00
universe . AssemblyResolve + = new IKVM . Reflection . ResolveEventHandler ( universe_AssemblyResolve ) ;
}
return rc ;
}
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 ) ;
if ( asm . Location ! = module . Location )
{
Console . Error . WriteLine ( "Warning: assembly '{0}' is ignored as previously loaded assembly '{1}' has the same identity '{2}'" , path , asm . Location , asm . FullName ) ;
}
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-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-05-11 10:11:03 +04:00
if ( previousMatch ! = null )
{
if ( previousMatchLevel = = 2 )
{
if ( HigherVersion ! = null )
{
HigherVersion ( previousMatch , name ) ;
}
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
}
}
private void AddLibraryPaths ( string str , string msg )
{
foreach ( string dir in str . Split ( Path . PathSeparator ) )
{
if ( Directory . Exists ( dir ) )
{
libpath . Add ( dir ) ;
}
else if ( dir ! = "" )
{
Console . Error . WriteLine ( "Warning: directory '{0}' specified in {1} is not valid" , dir , msg ) ;
}
}
}
private int LoadMscorlib ( IList < string > references )
{
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-11 08:15:29 +04:00
universe . LoadMscorlib ( r ) ;
2010-04-23 11:53:21 +04:00
return 0 ;
}
}
catch
2010-04-23 08:58:36 +04:00
{
}
}
}
foreach ( string mscorlib in FindAssemblyPath ( "mscorlib.dll" ) )
{
2010-05-11 08:15:29 +04:00
universe . LoadMscorlib ( mscorlib ) ;
2010-04-23 08:58:36 +04:00
return 0 ;
}
Console . Error . WriteLine ( "Error: unable to find mscorlib.dll" ) ;
return 1 ;
}
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 ) )
{
Console . WriteLine ( "Warning: Found assembly '{0}' using legacy search rule. Please append '.dll' to the reference." , file ) ;
yield return path ;
}
}
}
}
}
}