Use custom cecil assembly resolver
authorFraser Waters <frassle@gmail.com>
Thu, 7 Sep 2017 16:10:39 +0000 (17:10 +0100)
committerFraser Waters <frassle@gmail.com>
Tue, 26 Dec 2017 18:12:30 +0000 (18:12 +0000)
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.

src/Generator.Rewrite/Generator.Rewrite.csproj
src/Generator.Rewrite/OpenTKAssemblyResolver.cs [new file with mode: 0644]
src/Generator.Rewrite/Program.cs

index 38c3389..a726acc 100644 (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" />
diff --git a/src/Generator.Rewrite/OpenTKAssemblyResolver.cs b/src/Generator.Rewrite/OpenTKAssemblyResolver.cs
new file mode 100644 (file)
index 0000000..1bdd0a9
--- /dev/null
@@ -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() { }
+    }
+}
index c662088..6c65334 100644 (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;