Skip to content

Instantly share code, notes, and snippets.

@safern
Created August 29, 2020 05:46
Show Gist options
  • Save safern/f25baf0028e9db637e8bf169993f6058 to your computer and use it in GitHub Desktop.
Save safern/f25baf0028e9db637e8bf169993f6058 to your computer and use it in GitHub Desktop.
APICompat Loader with Roslyn
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