diff options
author | Vladislav Rishe <vlados.v10.0@gmail.com> | 2017-07-28 12:12:47 +0300 |
---|---|---|
committer | Marek Safar <marek.safar@gmail.com> | 2017-08-02 22:30:55 +0300 |
commit | c39f545b10306e5e0420501a0777f7288bbbbf24 (patch) | |
tree | 749f2a1d16c1647f2b98d235f5f9c01c8b6673d1 /linker | |
parent | 39b70e7550e38188a6f411d7ec46fc9ac5528cd8 (diff) |
Provide 'event' and 'property' elements support with Link XML step.
Diffstat (limited to 'linker')
9 files changed, 316 insertions, 35 deletions
diff --git a/linker/Mono.Linker.Steps/ResolveFromXmlStep.cs b/linker/Mono.Linker.Steps/ResolveFromXmlStep.cs index a74548730..1f4cc89c6 100644 --- a/linker/Mono.Linker.Steps/ResolveFromXmlStep.cs +++ b/linker/Mono.Linker.Steps/ResolveFromXmlStep.cs @@ -29,7 +29,7 @@ // using System; -using SR = System.Reflection; +using System.Collections.Generic; using System.Text; using System.Text.RegularExpressions; using System.Xml.XPath; @@ -51,8 +51,12 @@ namespace Mono.Linker.Steps { static readonly string _fullname = "fullname"; static readonly string _required = "required"; static readonly string _preserve = "preserve"; + static readonly string _accessors = "accessors"; static readonly string _ns = string.Empty; + static readonly string[] _accessorsAll = new string[] { "all" }; + static readonly char[] _accessorsSep = new char[] { ';' }; + XPathDocument _document; string _xmlDocumentLocation; @@ -161,7 +165,7 @@ namespace Mono.Linker.Steps { static Regex CreateRegexFromPattern (string pattern) { - return new Regex (pattern.Replace(".", @"\.").Replace("*", "(.*)")); + return new Regex (pattern.Replace (".", @"\.").Replace ("*", "(.*)")); } void MatchType (TypeDefinition type, Regex regex, XPathNavigator nav) @@ -204,7 +208,7 @@ namespace Mono.Linker.Steps { if (assembly.MainModule.HasExportedTypes) { foreach (var exported in assembly.MainModule.ExportedTypes) { - MatchExportedType(exported, assembly.MainModule, regex, nav); + MatchExportedType (exported, assembly.MainModule, regex, nav); } } } @@ -234,6 +238,8 @@ namespace Mono.Linker.Steps { if (nav.HasChildren) { MarkSelectedFields (nav, type); MarkSelectedMethods (nav, type); + MarkSelectedEvents (nav, type); + MarkSelectedProperties (nav, type); } } @@ -255,6 +261,24 @@ namespace Mono.Linker.Steps { ProcessMethods (type, methods); } + void MarkSelectedEvents (XPathNavigator nav, TypeDefinition type) + { + XPathNodeIterator events = nav.SelectChildren ("event", _ns); + if (events.Count == 0) + return; + + ProcessEvents (type, events); + } + + void MarkSelectedProperties (XPathNavigator nav, TypeDefinition type) + { + XPathNodeIterator properties = nav.SelectChildren ("property", _ns); + if (properties.Count == 0) + return; + + ProcessProperties (type, properties); + } + static TypePreserve GetTypePreserve (XPathNavigator nav) { string attribute = GetAttribute (nav, _preserve); @@ -324,7 +348,7 @@ namespace Mono.Linker.Steps { void ProcessMethods (TypeDefinition type, XPathNodeIterator iterator) { - while (iterator.MoveNext()) { + while (iterator.MoveNext ()) { string value = GetSignature (iterator.Current); if (!String.IsNullOrEmpty (value)) ProcessMethodSignature (type, value); @@ -344,12 +368,25 @@ namespace Mono.Linker.Steps { void MarkMethod (TypeDefinition type, MethodDefinition method, string signature) { if (method != null) { - Annotations.Mark (method); - Annotations.SetAction (method, MethodAction.Parse); + MarkMethod (method); } else AddUnresolveMarker (string.Format ("T: {0}; M: {1}", type, signature)); } + void MarkMethod (MethodDefinition method) + { + Annotations.Mark (method); + Annotations.SetAction (method, MethodAction.Parse); + } + + void MarkMethodIfNotNull (MethodDefinition method) + { + if (method == null) + return; + + MarkMethod (method); + } + void ProcessMethodName (TypeDefinition type, string name) { if (!type.HasMethods) @@ -389,6 +426,141 @@ namespace Mono.Linker.Steps { return sb.ToString (); } + void ProcessEvents (TypeDefinition type, XPathNodeIterator iterator) + { + while (iterator.MoveNext ()) { + string value = GetSignature (iterator.Current); + if (!String.IsNullOrEmpty (value)) + ProcessEventSignature (type, value); + + value = GetAttribute (iterator.Current, "name"); + if (!String.IsNullOrEmpty (value)) + ProcessEventName (type, value); + } + } + + void ProcessEventSignature (TypeDefinition type, string signature) + { + EventDefinition @event = GetEvent (type, signature); + MarkEvent (type, @event, signature); + } + + void MarkEvent (TypeDefinition type, EventDefinition @event, string signature) + { + if (@event != null) { + Annotations.Mark (@event); + + MarkMethod (@event.AddMethod); + MarkMethod (@event.RemoveMethod); + MarkMethodIfNotNull (@event.InvokeMethod); + } else + AddUnresolveMarker (string.Format ("T: {0}; E: {1}", type, signature)); + } + + void ProcessEventName (TypeDefinition type, string name) + { + if (!type.HasEvents) + return; + + foreach (EventDefinition @event in type.Events) + if (@event.Name == name) + MarkEvent (type, @event, name); + } + + static EventDefinition GetEvent (TypeDefinition type, string signature) + { + if (!type.HasEvents) + return null; + + foreach (EventDefinition @event in type.Events) + if (signature == GetEventSignature (@event)) + return @event; + + return null; + } + + static string GetEventSignature (EventDefinition @event) + { + return @event.EventType.FullName + " " + @event.Name; + } + + void ProcessProperties (TypeDefinition type, XPathNodeIterator iterator) + { + while (iterator.MoveNext ()) { + string value = GetSignature (iterator.Current); + if (!String.IsNullOrEmpty (value)) + ProcessPropertySignature (type, value, GetAccessors (iterator.Current)); + + value = GetAttribute (iterator.Current, "name"); + if (!String.IsNullOrEmpty (value)) + ProcessPropertyName (type, value, _accessorsAll); + } + } + + void ProcessPropertySignature (TypeDefinition type, string signature, string[] accessors) + { + PropertyDefinition property = GetProperty (type, signature); + MarkProperty (type, property, signature, accessors); + } + + void MarkProperty (TypeDefinition type, PropertyDefinition property, string signature, string[] accessors) + { + if (property != null) { + Annotations.Mark (property); + + MarkPropertyAccessors (type, property, accessors); + } else + AddUnresolveMarker (string.Format ("T: {0}; P: {1}", type, signature)); + } + + void MarkPropertyAccessors (TypeDefinition type, PropertyDefinition property, string[] accessors) + { + if (Array.IndexOf (accessors, "all") >= 0) { + MarkMethodIfNotNull (property.GetMethod); + MarkMethodIfNotNull (property.SetMethod); + + return; + } + if (property.GetMethod != null + && Array.IndexOf (accessors, "get") >= 0) + MarkMethod (property.GetMethod); + else if (property.GetMethod == null) + AddUnresolveMarker (string.Format ("T: {0}' M: {1} get_{2}", type, property.PropertyType, property.Name)); + + if (property.SetMethod != null + && Array.IndexOf (accessors, "set") >= 0) + MarkMethod (property.SetMethod); + else if (property.SetMethod == null) + AddUnresolveMarker (string.Format ("T: {0}' M: System.Void set_{2} ({1})", type, property.PropertyType, property.Name)); + } + + void ProcessPropertyName (TypeDefinition type, string name, string[] accessors) + { + if (!type.HasProperties) + return; + + foreach (PropertyDefinition property in type.Properties) + if (property.Name == name) + MarkProperty (type, property, name, accessors); + } + + static PropertyDefinition GetProperty (TypeDefinition type, string signature) + { + if (!type.HasProperties) + return null; + + foreach (PropertyDefinition property in type.Properties) + if (signature == GetPropertySignature (property)) + return property; + + return null; + } + + static string GetPropertySignature (PropertyDefinition property) + { + return property.PropertyType.FullName + " " + property.Name; + } + static AssemblyDefinition GetAssembly (LinkContext context, string assemblyName) { AssemblyNameReference reference = AssemblyNameReference.Parse (assemblyName); @@ -433,6 +605,24 @@ namespace Mono.Linker.Steps { return GetAttribute (nav, _fullname); } + static string[] GetAccessors (XPathNavigator nav) + { + string accessorsValue = GetAttribute (nav, _accessors); + + if (accessorsValue != null) { + string[] accessors = accessorsValue.Split ( + _accessorsSep, StringSplitOptions.RemoveEmptyEntries); + + if (accessors.Length > 0) { + for (int i = 0; i < accessors.Length; ++i) + accessors[i] = accessors[i].ToLower (); + + return accessors; + } + } + return _accessorsAll; + } + static string GetAttribute (XPathNavigator nav, string attribute) { return nav.GetAttribute (attribute, _ns); diff --git a/linker/Tests/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptBackingFieldAttribute.cs b/linker/Tests/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptBackingFieldAttribute.cs index a4160490a..b21ce0628 100644 --- a/linker/Tests/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptBackingFieldAttribute.cs +++ b/linker/Tests/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptBackingFieldAttribute.cs @@ -1,8 +1,7 @@ using System; -namespace Mono.Linker.Tests.Cases.Expectations.Assertions -{ - [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)] +namespace Mono.Linker.Tests.Cases.Expectations.Assertions { + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Event, AllowMultiple = false, Inherited = false)] public sealed class KeptBackingFieldAttribute : KeptAttribute { } } diff --git a/linker/Tests/Mono.Linker.Tests.Cases/LinkXml/UnusedEventPreservedByLinkXmlIsKept.cs b/linker/Tests/Mono.Linker.Tests.Cases/LinkXml/UnusedEventPreservedByLinkXmlIsKept.cs new file mode 100644 index 000000000..c17eedfb6 --- /dev/null +++ b/linker/Tests/Mono.Linker.Tests.Cases/LinkXml/UnusedEventPreservedByLinkXmlIsKept.cs @@ -0,0 +1,23 @@ +using System; + +using Mono.Linker.Tests.Cases.Expectations.Assertions; + +namespace Mono.Linker.Tests.Cases.LinkXml { + class UnusedEventPreservedByLinkXmlIsKept { + public static void Main () + { + } + + [Kept] + class Unused { + [Kept] + [KeptBackingField] + public event EventHandler<EventArgs> Preserved; + + [Kept] + public event EventHandler<EventArgs> Preserved1 { [Kept] add { } [Kept] remove { } } + + public event EventHandler<EventArgs> NotPreserved; + } + } +}
\ No newline at end of file diff --git a/linker/Tests/Mono.Linker.Tests.Cases/LinkXml/UnusedEventPreservedByLinkXmlIsKept.xml b/linker/Tests/Mono.Linker.Tests.Cases/LinkXml/UnusedEventPreservedByLinkXmlIsKept.xml new file mode 100644 index 000000000..6674d9784 --- /dev/null +++ b/linker/Tests/Mono.Linker.Tests.Cases/LinkXml/UnusedEventPreservedByLinkXmlIsKept.xml @@ -0,0 +1,8 @@ +<linker> + <assembly fullname="test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"> + <type fullname="Mono.Linker.Tests.Cases.LinkXml.UnusedEventPreservedByLinkXmlIsKept/Unused"> + <event signature="System.EventHandler`1<System.EventArgs> Preserved" /> + <event signature="System.EventHandler`1<System.EventArgs> Preserved1" /> + </type> + </assembly> +</linker>
\ No newline at end of file diff --git a/linker/Tests/Mono.Linker.Tests.Cases/LinkXml/UnusedPropertyPreservedByLinkXmlIsKept.cs b/linker/Tests/Mono.Linker.Tests.Cases/LinkXml/UnusedPropertyPreservedByLinkXmlIsKept.cs index 65084580f..1dbd59a8a 100644 --- a/linker/Tests/Mono.Linker.Tests.Cases/LinkXml/UnusedPropertyPreservedByLinkXmlIsKept.cs +++ b/linker/Tests/Mono.Linker.Tests.Cases/LinkXml/UnusedPropertyPreservedByLinkXmlIsKept.cs @@ -20,6 +20,22 @@ namespace Mono.Linker.Tests.Cases.LinkXml { [KeptBackingField] public int PreservedProperty3 { get; [Kept] set; } + [Kept] + [KeptBackingField] + public int PreservedProperty4 { [Kept] get; [Kept] set; } + + [Kept] + [KeptBackingField] + public int PreservedProperty5 { [Kept] get; [Kept] set; } + + [Kept] + [KeptBackingField] + public int PreservedProperty6 { [Kept] get; set; } + + [Kept] + [KeptBackingField] + public int PreservedProperty7 { get; [Kept] set; } + public int NotPreservedProperty { get; set; } } } diff --git a/linker/Tests/Mono.Linker.Tests.Cases/LinkXml/UnusedPropertyPreservedByLinkXmlIsKept.xml b/linker/Tests/Mono.Linker.Tests.Cases/LinkXml/UnusedPropertyPreservedByLinkXmlIsKept.xml index 1e182255d..de3fe0c7e 100644 --- a/linker/Tests/Mono.Linker.Tests.Cases/LinkXml/UnusedPropertyPreservedByLinkXmlIsKept.xml +++ b/linker/Tests/Mono.Linker.Tests.Cases/LinkXml/UnusedPropertyPreservedByLinkXmlIsKept.xml @@ -5,6 +5,11 @@ <method signature="System.Void set_PreservedProperty1(System.Int32)" /> <method signature="System.Int32 get_PreservedProperty2()" /> <method signature="System.Void set_PreservedProperty3(System.Int32)" /> + + <property signature="System.Int32 PreservedProperty4" /> + <property signature="System.Int32 PreservedProperty5" accessors="all" /> + <property signature="System.Int32 PreservedProperty6" accessors="get" /> + <property signature="System.Int32 PreservedProperty7" accessors="set" /> </type> </assembly> </linker>
\ No newline at end of file diff --git a/linker/Tests/Mono.Linker.Tests.Cases/Mono.Linker.Tests.Cases.csproj b/linker/Tests/Mono.Linker.Tests.Cases/Mono.Linker.Tests.Cases.csproj index a74c019af..be75f1d64 100644 --- a/linker/Tests/Mono.Linker.Tests.Cases/Mono.Linker.Tests.Cases.csproj +++ b/linker/Tests/Mono.Linker.Tests.Cases/Mono.Linker.Tests.Cases.csproj @@ -58,6 +58,7 @@ <Compile Include="Basic\UsedPropertyIsKept.cs" /> <Compile Include="Basic\UsedStructIsKept.cs" /> <Compile Include="LinkXml\UnusedAssemblyWithNoDefinedPreserveHasAllTypesPreserved.cs" /> + <Compile Include="LinkXml\UnusedEventPreservedByLinkXmlIsKept.cs" /> <Compile Include="LinkXml\UnusedTypeWithNoDefinedPreserveHasAllMembersPreserved.cs" /> <Compile Include="VirtualMethods\ClassUsedFromConcreteTypeHasInterfaceMethodRemoved.cs" /> <Compile Include="VirtualMethods\ClassUsedFromInterfaceHasInterfaceMethodKept.cs" /> @@ -133,6 +134,7 @@ <ItemGroup> <Content Include="LinkXml\TypeWithPreserveFieldsHasBackingFieldsOfPropertiesRemoved.xml" /> <Content Include="LinkXml\UnusedAssemblyWithNoDefinedPreserveHasAllTypesPreserved.xml" /> + <Content Include="LinkXml\UnusedEventPreservedByLinkXmlIsKept.xml" /> <Content Include="LinkXml\UnusedFieldPreservedByLinkXmlIsKept.xml" /> <Content Include="LinkXml\UnusedMethodPreservedByLinkXmlIsKept.xml" /> <Content Include="LinkXml\UnusedNestedTypePreservedByLinkXmlIsKept.xml" /> @@ -160,4 +162,4 @@ <Target Name="AfterBuild"> </Target> --> -</Project> +</Project>
\ No newline at end of file diff --git a/linker/Tests/TestCasesRunner/AssemblyChecker.cs b/linker/Tests/TestCasesRunner/AssemblyChecker.cs index 3d28c7ddf..7a5132b95 100644 --- a/linker/Tests/TestCasesRunner/AssemblyChecker.cs +++ b/linker/Tests/TestCasesRunner/AssemblyChecker.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -13,6 +13,7 @@ namespace Mono.Linker.Tests.TestCasesRunner { HashSet<string> linkedMembers; HashSet<string> verifiedBackingFields = new HashSet<string> (); + HashSet<string> verifiedEventMethods = new HashSet<string>(); public AssemblyChecker (AssemblyDefinition original, AssemblyDefinition linked) { @@ -96,6 +97,11 @@ namespace Mono.Linker.Tests.TestCasesRunner { VerifyProperty (p, linked?.Properties.FirstOrDefault (l => p.Name == l.Name), linked); linkedMembers.Remove (p.FullName); } + // Need to check events before fields so that the KeptBackingFieldAttribute is handled correctly + foreach (var e in original.Events) { + VerifyEvent (e, linked?.Events.FirstOrDefault (l => e.Name == l.Name), linked); + linkedMembers.Remove (e.FullName); + } foreach (var f in original.Fields) { if (verifiedBackingFields.Contains (f.FullName)) @@ -105,14 +111,11 @@ namespace Mono.Linker.Tests.TestCasesRunner { } foreach (var m in original.Methods) { + if (verifiedEventMethods.Contains (m.FullName)) + continue; VerifyMethod (m, linked?.Methods.FirstOrDefault (l => m.GetSignature () == l.GetSignature ())); linkedMembers.Remove (m.FullName); } - - foreach (var e in original.Events) { - VerifyEvent (e, linked?.Events.FirstOrDefault (l => e.Name == l.Name)); - linkedMembers.Remove (e.FullName); - } } void VerifyBaseType (TypeDefinition src, TypeDefinition linked) @@ -184,7 +187,7 @@ namespace Mono.Linker.Tests.TestCasesRunner { void VerifyProperty (PropertyDefinition src, PropertyDefinition linked, TypeDefinition linkedType) { - VerifyBackingField (src, linkedType); + VerifyMemberBackingField (src, linkedType); bool expectedKept = ShouldBeKept (src); @@ -204,8 +207,10 @@ namespace Mono.Linker.Tests.TestCasesRunner { VerifyCustomAttributes (src, linked); } - void VerifyEvent (EventDefinition src, EventDefinition linked) + void VerifyEvent (EventDefinition src, EventDefinition linked, TypeDefinition linkedType) { + VerifyMemberBackingField (src, linkedType); + bool expectedKept = ShouldBeKept (src); if (!expectedKept) { @@ -213,6 +218,22 @@ namespace Mono.Linker.Tests.TestCasesRunner { Assert.Fail ($"Event `{src}' should have been removed"); return; + } else { + var keptBackingFieldAttribute = src.CustomAttributes + .FirstOrDefault (attr => attr.AttributeType.Name == nameof (KeptBackingFieldAttribute)); + + // If we have KeepBackingFieldAttribute set, + // then we expect having 'add' and 'remove' accessors marked as 'kept' implicitly. + if (keptBackingFieldAttribute != null) + { + VerifyMethodInternal (src.AddMethod, linked.AddMethod, true); + verifiedEventMethods.Add (src.AddMethod.FullName); + linkedMembers.Remove (src.AddMethod.FullName); + + VerifyMethodInternal (src.RemoveMethod, linked.RemoveMethod, true); + verifiedEventMethods.Add (src.RemoveMethod.FullName); + linkedMembers.Remove (src.RemoveMethod.FullName); + } } if (linked == null) @@ -228,6 +249,12 @@ namespace Mono.Linker.Tests.TestCasesRunner { var srcSignature = src.GetSignature (); bool expectedKept = ShouldBeKept (src, srcSignature) || (linked != null && linked.DeclaringType.Module.EntryPoint == linked); + VerifyMethodInternal (src, linked, expectedKept); + } + + + void VerifyMethodInternal (MethodDefinition src, MethodDefinition linked, bool expectedKept) + { if (!expectedKept) { if (linked != null) Assert.Fail ($"Method `{src.FullName}' should have been removed"); @@ -244,13 +271,14 @@ namespace Mono.Linker.Tests.TestCasesRunner { VerifyCustomAttributes (src, linked); } - void VerifyBackingField (PropertyDefinition src, TypeDefinition linkedType) + void VerifyMemberBackingField (IMemberDefinition src, TypeDefinition linkedType) { var keptBackingFieldAttribute = src.CustomAttributes.FirstOrDefault (attr => attr.AttributeType.Name == nameof (KeptBackingFieldAttribute)); if (keptBackingFieldAttribute == null) return; - var backingFieldName = $"<{src.Name}>k__BackingField"; + var backingFieldName = src.MetadataToken.TokenType == TokenType.Property + ? $"<{src.Name}>k__BackingField" : src.Name; var srcField = src.DeclaringType.Fields.FirstOrDefault (f => f.Name == backingFieldName); if (srcField == null) { @@ -263,7 +291,7 @@ namespace Mono.Linker.Tests.TestCasesRunner { } if (srcField == null) - Assert.Fail ($"Property `{src}', could not locate the expected backing field {backingFieldName}"); + Assert.Fail ($"{src.MetadataToken.TokenType} `{src}', could not locate the expected backing field {backingFieldName}"); VerifyFieldKept (srcField, linkedType?.Fields.FirstOrDefault (l => srcField.Name == l.Name)); verifiedBackingFields.Add (srcField.FullName); @@ -317,8 +345,8 @@ namespace Mono.Linker.Tests.TestCasesRunner { static IEnumerable<T> GetCustomAttributeCtorValues<T> (ICustomAttributeProvider provider, string attributeName) where T : class { return provider.CustomAttributes. - Where (w => w.AttributeType.Name == attributeName && w.Constructor.Parameters.Count == 1). - Select (l => l.ConstructorArguments [0].Value as T); + Where (w => w.AttributeType.Name == attributeName && w.Constructor.Parameters.Count == 1). + Select (l => l.ConstructorArguments [0].Value as T); } } } diff --git a/linker/Tests/TestCasesRunner/PeVerifier.cs b/linker/Tests/TestCasesRunner/PeVerifier.cs index 388fa1e5d..0d87b63f5 100644 --- a/linker/Tests/TestCasesRunner/PeVerifier.cs +++ b/linker/Tests/TestCasesRunner/PeVerifier.cs @@ -9,8 +9,7 @@ using Mono.Linker.Tests.Extensions; using NUnit.Framework; namespace Mono.Linker.Tests.TestCasesRunner { - public class PeVerifier - { + public class PeVerifier { private readonly string _peExecutable; public PeVerifier () @@ -66,11 +65,11 @@ namespace Mono.Linker.Tests.TestCasesRunner { skipCheckEntirely = true; } else - throw new ArgumentException($"Unhandled platform and toolchain values of {Environment.OSVersion.Platform} and {skipToolchain}"); + throw new ArgumentException ($"Unhandled platform and toolchain values of {Environment.OSVersion.Platform} and {skipToolchain}"); } else if (ctorArg.Type.Name == nameof (String)) { assembliesToSkip.Add ((string)ctorArg.Value); } else { - throw new ArgumentException($"Unhandled constructor argument type of {ctorArg.Type} on {nameof (SkipPeVerifyAttribute)}"); + throw new ArgumentException ($"Unhandled constructor argument type of {ctorArg.Type} on {nameof (SkipPeVerifyAttribute)}"); } } } @@ -101,28 +100,39 @@ namespace Mono.Linker.Tests.TestCasesRunner { if (Environment.OSVersion.Platform != PlatformID.Win32NT) throw new InvalidOperationException ("This method should only be called on windows"); - var key = Registry.LocalMachine.OpenSubKey (@"SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows"); + NPath result; + if (TryFindPeExecutableFromRegustrySubfolder ("NETFXSDK", out result)) + return result; + if (TryFindPeExecutableFromRegustrySubfolder ("Windows", out result)) + return result; + + throw new InvalidOperationException ("Could not locate a peverify.exe executable"); + } + + private static bool TryFindPeExecutableFromRegustrySubfolder (string subfolder, out NPath peVerifyPath) + { + var keyPath = $"SOFTWARE\\Wow6432Node\\Microsoft\\Microsoft SDKs\\{subfolder}"; + var key = Registry.LocalMachine.OpenSubKey (keyPath); + foreach (var sdkKeyName in key.GetSubKeyNames ().OrderBy (name => new Version (name.TrimStart ('v').TrimEnd ('A'))).Reverse ()) { - var sdkKey = Registry.LocalMachine.OpenSubKey ($"SOFTWARE\\Wow6432Node\\Microsoft\\Microsoft SDKs\\Windows\\{sdkKeyName}"); + var sdkKey = Registry.LocalMachine.OpenSubKey ($"{keyPath}\\{sdkKeyName}"); var sdkDir = (string)sdkKey.GetValue ("InstallationFolder"); if (string.IsNullOrEmpty (sdkDir)) continue; var binDir = sdkDir.ToNPath ().Combine ("bin"); - if (!binDir.Exists ()) continue; foreach (var netSdkDirs in binDir.Directories ().OrderBy (dir => dir.FileName)) { - var peVerifyPath = netSdkDirs.Combine ("PEVerify.exe"); - + peVerifyPath = netSdkDirs.Combine ("PEVerify.exe"); if (peVerifyPath.FileExists ()) - return peVerifyPath; + return true; } } - - throw new InvalidOperationException ("Could not locate a peverify.exe executable"); + peVerifyPath = null; + return false; } } } |