-
Notifications
You must be signed in to change notification settings - Fork 533
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Xamarin.Android.Build.Tasks] use System.Reflection.Metadata in <Reso…
…lveAssemblies/> Context: https://github.com/dotnet/corefx/tree/master/src/System.Reflection.Metadata/src/System/Reflection/Metadata Context: https://github.com/jonathanpeppers/Benchmarks There is a new System.Reflection.Metadata library from corefx for reading .NET assemblies. It is a bit more performant than Mono.Cecil because it is a different library with different opinions. Some notes about System.Reflection.Metadata: - SRM has a forward "reader" style API - SRM uses lots of structs, and you have to do an additional call to lookup strings generally. - SRM, as far as I have seen, doesn't have APIs to modify and write out new assemblies. - SRM only supports "portable" pdb files. - SRM is not well documented yet. To discover usage, I read source code and/or unit tests. From my benchmark above, it seems that SRM is 10x faster on Windows/.NET framework and 5x faster on macOS/Mono. So it makes sense for use to use SRM when reading assemblies (and we don't need symbols), and continue with Mono.Cecil for the linker and other things that modify assemblies. There are a few places we can take advantage of SRM, but the simplest with a reasonable impact was `ResolveAssemblies`: Before: 320 ms ResolveAssemblies 1 calls After: 112 ms ResolveAssemblies 1 calls So a ~200ms savings on this MSBuild task, which runs on *every* build. This was the Xamarin.Forms test project in this repo: a build with no changes. ~~ Changes ~~ - Added a `MetadataResolver` type, as a way to cache `PEReader` instances. This is a comparable drop-in replacement for `DirectoryAssemblyResolver`. - `MonoAndroidHelper.IsReferenceAssembly` now uses `System.Reflection.Metadata` instead of `Mono.Cecil`. This is used in a few other MSBuild tasks. - A `MetadataExtensions` provides an extension method to simplify getting the full name of a custom attribute. We can add more here as needed. - Had to adjust the filename reported for XA2002, should optionally call `Path.GetFileNameWithoutExtension` if the name ends with `.dll`. The resulting code *should* be the same, except we are using SRM over Mono.Cecil. ~~ Other changes ~~ [xabuild.exe] remove SRM reference This appears to fix the build on macOS, we had this workaround from a mono bump in the past. Since xabuild has its own version of System.Reflection.Metadata that was already loaded, we weren't loading the one we are using in XA's MSBuild tasks. Things appear to work without the reference now. ~~ Downstream ~~ We will need to add the following assemblies to the installer: - `System.Reflection.Metadata.dll` - `System.Collections.Immutable.dll`
- Loading branch information
1 parent
434d2d8
commit 1da8240
Showing
8 changed files
with
199 additions
and
83 deletions.
There are no files selected for viewing
This file contains 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
32 changes: 32 additions & 0 deletions
32
src/Xamarin.Android.Build.Tasks/Utilities/DummyCustomAttributeProvider.cs
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
using System.Reflection.Metadata; | ||
|
||
namespace Xamarin.Android.Tasks | ||
{ | ||
/// <summary> | ||
/// A helper type for System.Reflection.Metadata. Getting the value of custom attribute arguments is a bit convoluted, if you merely want the values. | ||
/// | ||
/// This interface allows usage such as: | ||
/// CustomAttribute attribute = reader.GetCustomAttribute (handle); | ||
/// CustomAttributeValue<object> decoded = attribute.DecodeValue (DummyCustomAttributeProvider.Instance); | ||
/// </summary> | ||
public class DummyCustomAttributeProvider : ICustomAttributeTypeProvider<object> | ||
{ | ||
public static readonly DummyCustomAttributeProvider Instance = new DummyCustomAttributeProvider (); | ||
|
||
public object GetPrimitiveType (PrimitiveTypeCode typeCode) => null; | ||
|
||
public object GetSystemType () => null; | ||
|
||
public object GetSZArrayType (object elementType) => null; | ||
|
||
public object GetTypeFromDefinition (MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind) => null; | ||
|
||
public object GetTypeFromReference (MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind) => null; | ||
|
||
public object GetTypeFromSerializedName (string name) => null; | ||
|
||
public PrimitiveTypeCode GetUnderlyingEnumType (object type) => default (PrimitiveTypeCode); | ||
|
||
public bool IsSystemType (object type) => false; | ||
} | ||
} |
21 changes: 21 additions & 0 deletions
21
src/Xamarin.Android.Build.Tasks/Utilities/MetadataExtensions.cs
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
using System.Reflection.Metadata; | ||
|
||
namespace Xamarin.Android.Tasks | ||
{ | ||
public static class MetadataExtensions | ||
{ | ||
public static string GetCustomAttributeFullName (this MetadataReader reader, CustomAttribute attribute) | ||
{ | ||
if (attribute.Constructor.Kind == HandleKind.MemberReference) { | ||
var ctor = reader.GetMemberReference ((MemberReferenceHandle)attribute.Constructor); | ||
var type = reader.GetTypeReference ((TypeReferenceHandle)ctor.Parent); | ||
return reader.GetString (type.Namespace) + "." + reader.GetString (type.Name); | ||
} else if (attribute.Constructor.Kind == HandleKind.MethodDefinition) { | ||
var ctor = reader.GetMethodDefinition ((MethodDefinitionHandle)attribute.Constructor); | ||
var type = reader.GetTypeDefinition (ctor.GetDeclaringType ()); | ||
return reader.GetString (type.Namespace) + "." + reader.GetString (type.Name); | ||
} | ||
return null; | ||
} | ||
} | ||
} |
60 changes: 60 additions & 0 deletions
60
src/Xamarin.Android.Build.Tasks/Utilities/MetadataResolver.cs
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Reflection.Metadata; | ||
using System.Reflection.PortableExecutable; | ||
|
||
namespace Xamarin.Android.Tasks | ||
{ | ||
/// <summary> | ||
/// A replacement for DirectoryAssemblyResolver, using System.Reflection.Metadata | ||
/// </summary> | ||
public class MetadataResolver : IDisposable | ||
{ | ||
readonly Dictionary<string, PEReader> cache = new Dictionary<string, PEReader> (); | ||
readonly List<string> searchDirectories = new List<string> (); | ||
|
||
public MetadataReader GetAssemblyReader (string assemblyName) | ||
{ | ||
var key = Path.GetFileNameWithoutExtension (assemblyName); | ||
if (!cache.TryGetValue (key, out PEReader reader)) { | ||
var assemblyPath = Resolve (assemblyName); | ||
cache.Add (key, reader = new PEReader (File.OpenRead (assemblyPath))); | ||
} | ||
return reader.GetMetadataReader (); | ||
} | ||
|
||
public void AddSearchDirectory (string directory) | ||
{ | ||
directory = Path.GetFullPath (directory); | ||
if (!searchDirectories.Contains (directory)) | ||
searchDirectories.Add (directory); | ||
} | ||
|
||
public string Resolve (string assemblyName) | ||
{ | ||
string assemblyPath = assemblyName; | ||
if (!assemblyPath.EndsWith (".dll", StringComparison.OrdinalIgnoreCase)) { | ||
assemblyPath += ".dll"; | ||
} | ||
if (File.Exists (assemblyPath)) { | ||
return assemblyPath; | ||
} | ||
foreach (var dir in searchDirectories) { | ||
var path = Path.Combine (dir, assemblyPath); | ||
if (File.Exists (path)) | ||
return path; | ||
} | ||
|
||
throw new FileNotFoundException ($"Could not load assembly '{assemblyName}'.", assemblyName); | ||
} | ||
|
||
public void Dispose () | ||
{ | ||
foreach (var provider in cache.Values) { | ||
provider.Dispose (); | ||
} | ||
cache.Clear (); | ||
} | ||
} | ||
} |
Oops, something went wrong.