Use custom cecil assembly resolver

Fixes #642, until https://github.com/jbevain/cecil/pull/477 is merged
and released and we can update our Mono.Cecil dependency to that
version.

This custom resolver takes into account Mono reference assemblies for
mscorlib. This ensures we don't add spurious references to mscorlib
4.0.
This commit is contained in:
Fraser Waters 2017-09-07 17:10:39 +01:00
parent 6ba91adfbe
commit 084dcbf062
3 changed files with 307 additions and 0 deletions

View file

@ -59,6 +59,7 @@
<ItemGroup>
<Compile Include="CountAttribute.cs" />
<Compile Include="GeneratedVariableIdentifier.cs" />
<Compile Include="OpenTKAssemblyResolver.cs" />
<Compile Include="Options.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />

View file

@ -0,0 +1,305 @@
using Mono.Cecil;
using System;
using System.Collections.Generic;
using System.IO;
namespace OpenTK.Rewrite
{
/// This is based on the Mono.Cecil BaseAssemblyResolver. But with
/// additions to GetCorlib but with some extra code to check for -api
/// directories which we're added by mono in the 4.4 release. Without this
/// we can end up adding assembly references to mscorlib 4.0 even though
/// OpenTK is compiled for 2.0.
internal sealed class OpenTKAssemblyResolver : IAssemblyResolver
{
static readonly bool on_mono = Type.GetType("Mono.Runtime") != null;
readonly List<string> directories;
List<string> gac_paths;
public OpenTKAssemblyResolver()
{
directories = new List<string>(2) { ".", "bin" };
}
AssemblyDefinition GetAssembly(string file, ReaderParameters parameters)
{
if (parameters.AssemblyResolver == null)
parameters.AssemblyResolver = this;
return ModuleDefinition.ReadModule(file, parameters).Assembly;
}
public AssemblyDefinition Resolve(AssemblyNameReference name)
{
return Resolve(name, new ReaderParameters());
}
public AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters)
{
var assembly = SearchDirectory(name, directories, parameters);
if (assembly != null)
return assembly;
if (name.IsRetargetable)
{
// if the reference is retargetable, zero it
name = new AssemblyNameReference(name.Name, new Version(0, 0, 0, 0))
{
PublicKeyToken = new byte[0],
};
}
var framework_dir = Path.GetDirectoryName(typeof(object).Module.FullyQualifiedName);
var framework_dirs = on_mono
? new[] { framework_dir, Path.Combine(framework_dir, "Facades") }
: new[] { framework_dir };
if (IsZero(name.Version))
{
assembly = SearchDirectory(name, framework_dirs, parameters);
if (assembly != null)
return assembly;
}
if (name.Name == "mscorlib")
{
assembly = GetCorlib(name, parameters);
if (assembly != null)
return assembly;
}
assembly = GetAssemblyInGac(name, parameters);
if (assembly != null)
return assembly;
assembly = SearchDirectory(name, framework_dirs, parameters);
if (assembly != null)
return assembly;
throw new AssemblyResolutionException(name);
}
AssemblyDefinition SearchDirectory(AssemblyNameReference name, IEnumerable<string> directories, ReaderParameters parameters)
{
var extensions = name.IsWindowsRuntime ? new[] { ".winmd", ".dll" } : new[] { ".exe", ".dll" };
foreach (var directory in directories)
{
foreach (var extension in extensions)
{
string file = Path.Combine(directory, name.Name + extension);
if (!File.Exists(file))
continue;
try
{
var assembly = GetAssembly(file, parameters);
if (assembly.Name.Version == name.Version || IsZero(name.Version))
{
return assembly;
}
}
catch (System.BadImageFormatException)
{
continue;
}
}
}
return null;
}
static bool IsZero(Version version)
{
return version.Major == 0 && version.Minor == 0 && version.Build == 0 && version.Revision == 0;
}
AssemblyDefinition GetCorlib(AssemblyNameReference reference, ReaderParameters parameters)
{
var version = reference.Version;
var corlib = typeof(object).Assembly.GetName();
if (corlib.Version == version || IsZero(version))
return GetAssembly(typeof(object).Module.FullyQualifiedName, parameters);
var path = Directory.GetParent(
Directory.GetParent(
typeof(object).Module.FullyQualifiedName).FullName
).FullName;
if (on_mono)
{
if (version.Major == 1)
path = Path.Combine(path, "1.0");
else if (version.Major == 2)
{
if (version.MajorRevision == 5)
path = Path.Combine(path, "2.1");
else
path = Path.Combine(path, "2.0");
}
else if (version.Major == 4)
path = Path.Combine(path, "4.0");
else
throw new NotSupportedException("Version not supported: " + version);
}
else
{
switch (version.Major)
{
case 1:
if (version.MajorRevision == 3300)
path = Path.Combine(path, "v1.0.3705");
else
path = Path.Combine(path, "v1.0.5000.0");
break;
case 2:
path = Path.Combine(path, "v2.0.50727");
break;
case 4:
path = Path.Combine(path, "v4.0.30319");
break;
default:
throw new NotSupportedException("Version not supported: " + version);
}
}
var file = Path.Combine(path, "mscorlib.dll");
if (File.Exists(file))
return GetAssembly(file, parameters);
else if (on_mono && Directory.Exists(path + "-api"))
{
path = path + "-api";
file = Path.Combine(path, "mscorlib.dll");
if (File.Exists(file))
return GetAssembly(file, parameters);
}
return null;
}
static List<string> GetGacPaths()
{
if (on_mono)
return GetDefaultMonoGacPaths();
var paths = new List<string>(2);
var windir = Environment.GetEnvironmentVariable("WINDIR");
if (windir == null)
return paths;
paths.Add(Path.Combine(windir, "assembly"));
paths.Add(Path.Combine(windir, Path.Combine("Microsoft.NET", "assembly")));
return paths;
}
static List<string> GetDefaultMonoGacPaths()
{
var paths = new List<string>(1);
var gac = GetCurrentMonoGac();
if (gac != null)
paths.Add(gac);
var gac_paths_env = Environment.GetEnvironmentVariable("MONO_GAC_PREFIX");
if (string.IsNullOrEmpty(gac_paths_env))
return paths;
var prefixes = gac_paths_env.Split(Path.PathSeparator);
foreach (var prefix in prefixes)
{
if (string.IsNullOrEmpty(prefix))
continue;
var gac_path = Path.Combine(Path.Combine(Path.Combine(prefix, "lib"), "mono"), "gac");
if (Directory.Exists(gac_path) && !paths.Contains(gac))
paths.Add(gac_path);
}
return paths;
}
static string GetCurrentMonoGac()
{
return Path.Combine(
Directory.GetParent(
Path.GetDirectoryName(typeof(object).Module.FullyQualifiedName)).FullName,
"gac");
}
AssemblyDefinition GetAssemblyInGac(AssemblyNameReference reference, ReaderParameters parameters)
{
if (reference.PublicKeyToken == null || reference.PublicKeyToken.Length == 0)
return null;
if (gac_paths == null)
gac_paths = GetGacPaths();
if (on_mono)
return GetAssemblyInMonoGac(reference, parameters);
return GetAssemblyInNetGac(reference, parameters);
}
AssemblyDefinition GetAssemblyInMonoGac(AssemblyNameReference reference, ReaderParameters parameters)
{
for (int i = 0; i < gac_paths.Count; i++)
{
var gac_path = gac_paths[i];
var file = GetAssemblyFile(reference, string.Empty, gac_path);
if (File.Exists(file))
{
var assembly = GetAssembly(file, parameters);
if (assembly.Name.Version == reference.Version || IsZero(reference.Version))
{
return assembly;
}
}
}
return null;
}
AssemblyDefinition GetAssemblyInNetGac(AssemblyNameReference reference, ReaderParameters parameters)
{
var gacs = new[] { "GAC_MSIL", "GAC_32", "GAC_64", "GAC" };
var prefixes = new[] { string.Empty, "v4.0_" };
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < gacs.Length; j++)
{
var gac = Path.Combine(gac_paths[i], gacs[j]);
var file = GetAssemblyFile(reference, prefixes[i], gac);
if (Directory.Exists(gac) && File.Exists(file))
{
var assembly = GetAssembly(file, parameters);
if (assembly.Name.Version == reference.Version || IsZero(reference.Version))
{
return assembly;
}
}
}
}
return null;
}
static string GetAssemblyFile(AssemblyNameReference reference, string prefix, string gac)
{
var gac_folder = new System.Text.StringBuilder()
.Append(prefix)
.Append(reference.Version)
.Append("__");
for (int i = 0; i < reference.PublicKeyToken.Length; i++)
gac_folder.Append(reference.PublicKeyToken[i].ToString("x2"));
return Path.Combine(
Path.Combine(
Path.Combine(gac, reference.Name), gac_folder.ToString()),
reference.Name + ".dll");
}
void IDisposable.Dispose() { }
}
}

View file

@ -72,6 +72,7 @@ namespace OpenTK.Rewrite
var read_params = new ReaderParameters();
var write_params = new WriterParameters();
read_params.AssemblyResolver = new OpenTKAssemblyResolver();
read_params.ReadSymbols = true;
read_params.ReadWrite = true;
write_params.WriteSymbols = true;