Created
August 29, 2020 05:46
-
-
Save safern/f25baf0028e9db637e8bf169993f6058 to your computer and use it in GitHub Desktop.
APICompat Loader with Roslyn
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.CodeAnalysis; | |
using Microsoft.CodeAnalysis.CSharp; | |
using System; | |
using System.Collections.Generic; | |
using System.Collections.Immutable; | |
using System.ComponentModel; | |
using System.IO; | |
using System.Text; | |
using System.Text.RegularExpressions; | |
namespace TryRoslynCompilation | |
{ | |
internal class AssemblyLoader | |
{ | |
private readonly Dictionary<string, MetadataReference> _loadedAssemblies; | |
private CSharpCompilation _cSharpCompilation; | |
private readonly List<string> _assemblyDirs; | |
internal AssemblyLoader() | |
{ | |
_loadedAssemblies = new Dictionary<string, MetadataReference>(); | |
_cSharpCompilation = CSharpCompilation.Create($"AssemblyLoader-{DateTime.Now.ToString("MM-dd-yy-HH:mm:ss.FFF")}"); | |
_assemblyDirs = new List<string>(); | |
} | |
internal IEnumerable<IAssemblySymbol> LoadAssemblies(IEnumerable<AssemblyIdentity> identities) | |
{ | |
List<IAssemblySymbol> matchingAssemblies = new List<IAssemblySymbol>(); | |
foreach (AssemblyIdentity unmappedIdentity in identities) | |
{ | |
IAssemblySymbol matchingAssembly = LoadAssemblyFromIdentity(unmappedIdentity); | |
if (matchingAssembly == null) | |
{ | |
// TODO: add error | |
continue; | |
} | |
if (!matchingAssembly.Identity.Version.Equals(unmappedIdentity.Version)) | |
{ | |
// TODO: add warning | |
Console.WriteLine($"Found '{matchingAssembly.Identity.Name}' with version '{matchingAssembly.Identity.Version}' instead of '{unmappedIdentity.Version}'."); | |
} | |
string unmappedPkt = unmappedIdentity.HasPublicKey ? GetPublicKeyToken(unmappedIdentity.PublicKeyToken) : string.Empty; | |
string matchingPkt = matchingAssembly.Identity.HasPublicKey ? GetPublicKeyToken(matchingAssembly.Identity.PublicKeyToken) : string.Empty; | |
if (!matchingPkt.Equals(unmappedPkt)) | |
{ | |
// TODO: add warning | |
Console.WriteLine($"Found '{matchingAssembly.Identity.Name}' with PublicKeyToken '{matchingPkt}' instead of '{unmappedPkt}'."); | |
} | |
matchingAssemblies.Add(matchingAssembly); | |
} | |
return matchingAssemblies; | |
} | |
private string GetPublicKeyToken(ImmutableArray<byte> publicKeyToken) | |
{ | |
return string.Create(publicKeyToken.Length * 2, publicKeyToken, (dst, v) => | |
{ | |
for (int i = 0; i < publicKeyToken.Length; i++) | |
{ | |
Span<char> tmp = dst.Slice(i * 2, 2); | |
ReadOnlySpan<char> byteString = publicKeyToken[i].ToString("x2"); | |
byteString.CopyTo(tmp); | |
} | |
}); | |
} | |
private IAssemblySymbol LoadAssemblyFromIdentity(AssemblyIdentity unmappedIdentity) | |
{ | |
foreach (string probeDir in _assemblyDirs) | |
{ | |
string path = Path.Combine(probeDir, unmappedIdentity.Name + ".dll"); | |
if (File.Exists(path)) | |
{ | |
MetadataReference reference = CreateMetadataReferenceIfNeeded(path); | |
ISymbol symbol = _cSharpCompilation.GetAssemblyOrModuleSymbol(reference); | |
if (symbol is IAssemblySymbol assemblySymbol) | |
return assemblySymbol; | |
} | |
} | |
return null; | |
} | |
internal void LoadReferences(string paths) | |
{ | |
string[] assemblyPaths = paths.Split(","); | |
if (assemblyPaths.Length == 0) | |
{ | |
return; | |
} | |
LoadFromPaths(assemblyPaths); | |
} | |
internal IEnumerable<IAssemblySymbol> LoadAssemblies(string paths) | |
{ | |
string[] assemblyPaths = paths.Split(","); | |
if (assemblyPaths.Length == 0) | |
{ | |
yield break; | |
} | |
IEnumerable<MetadataReference> assembliesToReturn = LoadFromPaths(assemblyPaths); | |
foreach (MetadataReference assembly in assembliesToReturn) | |
{ | |
ISymbol symbol = _cSharpCompilation.GetAssemblyOrModuleSymbol(assembly); | |
if (symbol is IAssemblySymbol assemblySymbol) | |
yield return assemblySymbol; | |
} | |
} | |
private IEnumerable<MetadataReference> LoadFromPaths(IEnumerable<string> paths) | |
{ | |
List<MetadataReference> result = new List<MetadataReference>(); | |
foreach (string path in paths) | |
{ | |
if (Directory.Exists(path)) | |
{ | |
_assemblyDirs.Add(path); | |
result.AddRange(LoadAssembliesFromDirectory(path)); | |
} | |
else if (File.Exists(path)) | |
{ | |
_assemblyDirs.Add(Path.GetDirectoryName(path)); | |
result.Add(CreateMetadataReferenceIfNeeded(path)); | |
} | |
} | |
return result; | |
} | |
private IEnumerable<MetadataReference> LoadAssembliesFromDirectory(string directory) | |
{ | |
foreach (string assembly in Directory.EnumerateFiles(directory, "*.dll")) | |
{ | |
yield return CreateMetadataReferenceIfNeeded(assembly); | |
} | |
} | |
private MetadataReference CreateMetadataReferenceIfNeeded(string assembly) | |
{ | |
// Roslyn doesn't support having two assemblies as references with the same identity and then getting the symbol for it. | |
string fileName = Path.GetFileName(assembly); | |
if (!_loadedAssemblies.TryGetValue(fileName, out MetadataReference reference)) | |
{ | |
reference = MetadataReference.CreateFromFile(assembly); | |
_loadedAssemblies.Add(fileName, reference); | |
_cSharpCompilation = _cSharpCompilation.AddReferences(new MetadataReference[] { reference }); | |
} | |
return reference; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment