forked from Skippeh/FishNetV3.CodeGenerator
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPostProcessorAssemblyResolver.cs
186 lines (156 loc) · 6.71 KB
/
PostProcessorAssemblyResolver.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
using Mono.Cecil;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using Unity.CompilationPipeline.Common.ILPostProcessing;
namespace FishNet.CodeGenerating
{
public class PostProcessorAssemblyResolver : BaseAssemblyResolver
{
public List<string> AssemblySearchPaths = [];
private static readonly string[] IgnoreResolveAssemblies =
[
"FishNet.Runtime.dll"
];
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 override AssemblyDefinition Resolve(AssemblyNameReference name) => Resolve(name, new ReaderParameters(ReadingMode.Deferred));
public override AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters)
{
if (name.Name == "mscorlib" || name.Name == "netstandard")
return base.Resolve(name, parameters);
lock (m_AssemblyCache)
{
if (name.Name == m_CompiledAssembly.Name)
{
return m_SelfAssembly;
}
var fileName = FindFile(name);
bool pathResolved = false;
if (fileName == null)
{
fileName = ResolveAssemblyPath($"{name.Name}.dll");
pathResolved = true;
}
if (fileName == null)
{
return base.Resolve(name, parameters);
}
// Try to resolve absolute file path to assembly
if (!pathResolved)
fileName = ResolveAssemblyPath(fileName);
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 ResolveAssemblyPath(string assemblyPath)
{
if (Path.IsPathRooted(assemblyPath))
return assemblyPath;
if (IgnoreResolveAssemblies.Contains(assemblyPath))
{
var currentDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
return Path.Combine(currentDirectory!, assemblyPath);
}
foreach (var searchPath in AssemblySearchPaths)
{
var filePath = Path.Combine(searchPath, assemblyPath);
if (File.Exists(filePath))
{
Console.WriteLine($"Resolved assembly {assemblyPath}: {filePath}");
return filePath;
}
}
Console.WriteLine($"Failed to resolve path to assembly: {assemblyPath}");
return assemblyPath;
}
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(3, 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;
}
}
}