forked from cgvr/DeltaVR
		
	
		
			
				
	
	
		
			139 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			139 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
using MonoFN.Cecil;
 | 
						|
using System;
 | 
						|
using System.Collections.Generic;
 | 
						|
using System.IO;
 | 
						|
using System.Linq;
 | 
						|
using System.Threading;
 | 
						|
using Unity.CompilationPipeline.Common.ILPostProcessing;
 | 
						|
 | 
						|
namespace FishNet.CodeGenerating
 | 
						|
{
 | 
						|
    internal class PostProcessorAssemblyResolver : IAssemblyResolver
 | 
						|
    {
 | 
						|
        private readonly string[] m_AssemblyReferences;
 | 
						|
        private readonly Dictionary<string, AssemblyDefinition> m_AssemblyCache = new Dictionary<string, AssemblyDefinition>();
 | 
						|
        private readonly ICompiledAssembly m_CompiledAssembly;
 | 
						|
        private AssemblyDefinition m_SelfAssembly;
 | 
						|
 | 
						|
        public PostProcessorAssemblyResolver(ICompiledAssembly compiledAssembly)
 | 
						|
        {
 | 
						|
            m_CompiledAssembly = compiledAssembly;
 | 
						|
            m_AssemblyReferences = compiledAssembly.References;
 | 
						|
        }
 | 
						|
 | 
						|
        public void Dispose() { }
 | 
						|
 | 
						|
        public AssemblyDefinition Resolve(AssemblyNameReference name) => Resolve(name, new ReaderParameters(ReadingMode.Deferred));
 | 
						|
 | 
						|
        public AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters)
 | 
						|
        {
 | 
						|
            lock (m_AssemblyCache)
 | 
						|
            {
 | 
						|
                if (name.Name == m_CompiledAssembly.Name)
 | 
						|
                {
 | 
						|
                    return m_SelfAssembly;
 | 
						|
                }
 | 
						|
 | 
						|
                var fileName = FindFile(name);
 | 
						|
                if (fileName == null)
 | 
						|
                {
 | 
						|
                    return null;
 | 
						|
                }
 | 
						|
 | 
						|
                var lastWriteTime = File.GetLastWriteTime(fileName);
 | 
						|
                var cacheKey = $"{fileName}{lastWriteTime}";
 | 
						|
                if (m_AssemblyCache.TryGetValue(cacheKey, out var result))
 | 
						|
                {
 | 
						|
                    return result;
 | 
						|
                }
 | 
						|
 | 
						|
                parameters.AssemblyResolver = this;
 | 
						|
 | 
						|
                var ms = MemoryStreamFor(fileName);
 | 
						|
                var pdb = $"{fileName}.pdb";
 | 
						|
                if (File.Exists(pdb))
 | 
						|
                {
 | 
						|
                    parameters.SymbolStream = MemoryStreamFor(pdb);
 | 
						|
                }
 | 
						|
 | 
						|
                var assemblyDefinition = AssemblyDefinition.ReadAssembly(ms, parameters);
 | 
						|
                m_AssemblyCache.Add(cacheKey, assemblyDefinition);
 | 
						|
 | 
						|
                return assemblyDefinition;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        private string FindFile(AssemblyNameReference name)
 | 
						|
        {
 | 
						|
            var fileName = m_AssemblyReferences.FirstOrDefault(r => Path.GetFileName(r) == $"{name.Name}.dll");
 | 
						|
            if (fileName != null)
 | 
						|
            {
 | 
						|
                return fileName;
 | 
						|
            }
 | 
						|
 | 
						|
            // perhaps the type comes from an exe instead
 | 
						|
            fileName = m_AssemblyReferences.FirstOrDefault(r => Path.GetFileName(r) == $"{name.Name}.exe");
 | 
						|
            if (fileName != null)
 | 
						|
            {
 | 
						|
                return fileName;
 | 
						|
            }
 | 
						|
 | 
						|
            //Unfortunately the current ICompiledAssembly API only provides direct references.
 | 
						|
            //It is very much possible that a postprocessor ends up investigating a type in a directly
 | 
						|
            //referenced assembly, that contains a field that is not in a directly referenced assembly.
 | 
						|
            //if we don't do anything special for that situation, it will fail to resolve.  We should fix this
 | 
						|
            //in the ILPostProcessing API. As a workaround, we rely on the fact here that the indirect references
 | 
						|
            //are always located next to direct references, so we search in all directories of direct references we
 | 
						|
            //got passed, and if we find the file in there, we resolve to it.
 | 
						|
            return m_AssemblyReferences
 | 
						|
                .Select(Path.GetDirectoryName)
 | 
						|
                .Distinct()
 | 
						|
                .Select(parentDir => Path.Combine(parentDir, $"{name.Name}.dll"))
 | 
						|
                .FirstOrDefault(File.Exists);
 | 
						|
        }
 | 
						|
 | 
						|
        private static MemoryStream MemoryStreamFor(string fileName)
 | 
						|
        {
 | 
						|
            return Retry(10, TimeSpan.FromSeconds(1), () =>
 | 
						|
            {
 | 
						|
                byte[] byteArray;
 | 
						|
                using (var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
 | 
						|
                {
 | 
						|
                    byteArray = new byte[fs.Length];
 | 
						|
                    var readLength = fs.Read(byteArray, 0, (int)fs.Length);
 | 
						|
                    if (readLength != fs.Length)
 | 
						|
                    {
 | 
						|
                        throw new InvalidOperationException("File read length is not full length of file.");
 | 
						|
                    }
 | 
						|
                }
 | 
						|
 | 
						|
                return new MemoryStream(byteArray);
 | 
						|
            });
 | 
						|
        }
 | 
						|
 | 
						|
        private static MemoryStream Retry(int retryCount, TimeSpan waitTime, Func<MemoryStream> func)
 | 
						|
        {
 | 
						|
            try
 | 
						|
            {
 | 
						|
                return func();
 | 
						|
            }
 | 
						|
            catch (IOException)
 | 
						|
            {
 | 
						|
                if (retryCount == 0)
 | 
						|
                {
 | 
						|
                    throw;
 | 
						|
                }
 | 
						|
 | 
						|
                Console.WriteLine($"Caught IO Exception, trying {retryCount} more times");
 | 
						|
                Thread.Sleep(waitTime);
 | 
						|
 | 
						|
                return Retry(retryCount - 1, waitTime, func);
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        public void AddAssemblyDefinitionBeingOperatedOn(AssemblyDefinition assemblyDefinition)
 | 
						|
        {
 | 
						|
            m_SelfAssembly = assemblyDefinition;
 | 
						|
        }
 | 
						|
    }
 | 
						|
} |